Dungeon Souls Editor Quick Onboarding Modules Items Sprites Functions Steam Workshop Contact

Getting Started with Dungeon Souls Modding

Disclaimer: This documentation, the editor and modding support are still in alpha version, so expect more features to arrive (and please be a little patient with certain issues)!

Dungeon Souls is a rogue-like, dungeon crawler game made with Game Maker Studio 1.4. Chances are that, if you're reading this, you probably already know this fact. However, since it was developed with Game Maker Studio 1.4, there is an important concept to keep in mind: the base engine of Dungeon Souls is not meant to support modding completely. The default engine just supports media asset addition such as sprites and audio. As such, two programming languages have been created for this purpose.

The first, called Dungeon Souls Modding Language (DSML) is a high-level language to code mods for Dungeon Souls and primarly the one you'll be using. The second, called Dungeon Souls Assembly Language (DSAL) is a low-level language used by Dungeon Souls' game engine to enable modding support. The Dungeon Souls Modding Editor (DSEditor) will convert from DSML to DSAL, so there is no need to actually change the DSAL, unless you know what you're doing. It is strongly recommended not to change any DSAL files.

Below is a quick tutorial to get you started with your first modding project! If you have programming experience, you may find the Quick Onboarding page more suitable.

Creating Your First Project

When you first boot up DSEditor, your Projects folder will be empty. Right clicking on this folder, or selecting File>New Module (CTRL+N), will allow you to create a new Project Module. A Project Module is a project with the focus of developing a module. A module is a modification to the game. As part of the tutorial, perform the action of creating a new Project Module.

After requesting to create a new Project Module, a window will appear containing the following elements to be filled:

  • Name (required) - The name of the module, which will be shown in-game.
  • Version (required) - The version of the module, which will be displayed to the user during game. The version, even though not checked for correct incrementation, is a useful information for the player to know if they have the latest version of your module, so make sure you change it everytime you make significant changes.
  • Description - The description of the module that will be shown during game.

For the sake of this tutorial, we'll input the following information into each field:

  • Name (required) - MyModule
  • Version (required) - 1.0.0
  • Description - My first module for Dungeon Souls!

After the appropriate information is filled in, a new Project Module will appear under Projects with the name specified previously. Opening the project's node will show all of the modding files associated to the project. In this case, only one file will be present: the Dungeon Souls Module (.dsmod) file. This is the file where the module is declared, as well as any global variables (we'll soon talk about this concept) to the entire module. If you've been following along, the file should look like this:

define module test
{
[NAME]="MyModule";
[VERSION]="1.0.0";
[DESCRIPTION]="My first module for Dungeon Souls!";
[SPRITE_DIR]="Sprites";

//Place code here...
}

If you look at the code, you can see some familiar information. The name, version and description values inserted in the previous window are already set to their corresponding variables. In DSML, certain types of code (often referred as definitions) will have certain types of Game Variables. These represent a special kind of variable, which set important variables for the definition to work properly before any code is executed. Game Variables have the following syntax:

[<NAME_OF_THE_GAME_VARIABLE>]

Apart from the Game Variables specified previously, you can also check that there is one extra game variable called SPRITE_DIR. This game variable specifies the directory where the graphical assets will be stored, so that the engine knows where to load the sprites from. Currently, this value cannot be changed (changing it will cause an error on Dungeon Souls), but is planned to be able to accept any value, as to customize sprite location.

Finally, there is one more element in the code, which is the comment. Comments are pieces of text that are ignored by the compiler, but allow for easy documentation on the code. Use them whenever you need to explain an important concept or set a friendly reminder of what a particular piece of code does in your module. Comments start with // and currently do not possess multiple lines.

Definition Layout

Before we venture further on into our first module, it is important to layout the current definitions of the DSML. As stated previously, in the DSML, a definition corresponds to a declaration of a game asset. It tells the modding engine that it needs to define a certain game asset, based on what the definition descibres in its code. In comparison to other languages, it is similar to classes, sharing a few key concepts. In the navigator on the top of the page, you can find documentation for each of the definitions currently available to DSEditor.

Currently, DSML supports the following definitions:

  • Module - represents the definition of a new module.
  • Function - represents the definition of a new function. Functions are global within a module, meaning that, regardless of the location of its declaration, every other definition can access the function. However, currently, modules cannot access other modules' functions.
  • Item - represents the definition of a new Dungeon Souls Item. This item has several properties, which we will see below, and can be spawned by chests, depending on its rarity. Currently, arcane forge items are not supported.
  • Sprite - represents the definition of a new sprite. Sprites are graphical assets that can be used in any definition that requires a graphical representaton, such as items.

In this tutorial, we have already taken a look at a module definition. We will also create a sprite, function and item using their respective definitions. We will also take a brief look at the syntax and features of DSML. To do this, we'll create a legendary active item that kills all nearby enemies. For each enemy killed, the player gains extra max hp.

Creating a Sprite

In order to have custom graphical images in the game, a sprite must be defined. A sprite can be perceived as a collection of images. If the collection only has one image (often called subimage), then the sprite can be effectively treated as an image. However, if the sprite contains more than one subimage, then the sprite can be considered as an animation.

Before we define a sprite, we must add an image file to our project. To do so, we can do so in two ways:

  1. Right-click on the target project, then select Add Existing...>Image
  2. Selecting the desired target project on the Project Tree, then clicking the quick action Add Image
Performing any of these actions will open a file choose dialog box to select the desired image to load (check the Sprite Definition Reference for more information on the types of images allowed). For this tutorial, we'll choose an image of a gem, with size 24x24. When the editor adds an image, it will appear under the Images folder of the project. The editor automatically sorts any content that you add to a project, for organization's sake.

After adding the image to the project, we can now define our sprite. Similarly to the image addition, creating a sprite can also be done in the following two ways:

  1. Right-click on the target project, then select Create New...>Sprite
  2. Selecting the desired target project on the Project Tree, then clicking the quick action New Sprite
After a request to create a sprite has been performed, a window will appear, asking the name of the sprite. A good naming convention for sprites is to always start their name with spr. Furthermore, we will use the camelCase convention when naming variables (camelCase states that spaces are removed and the first letter of the subsequent word is capitalized. For example, "Lorem Ipsum" would be translated to "loremIpsum" in camelCase notation). As such, we'll name ours sprLifeAlchemyGem. After the code has been generated, we set the Game Variable [SUBIMAGE]'s value with the relative path to the image (which, in this case, is just the image itself), as such:

define sprite sprLifeAlchemyGem
{
[SUBIMAGE]="spr_Gem.png";


//Sprites can have multiple [SUBIMAGE] fields.
//The order of the sprite's animation is defined by the order of appearance of each [SUBIMAGE]
}

As specified in the comments, the order of the [SUBIMAGE] determines the order of the animation. The modding system determines the order in a top-down fashion, meaning that if, for example, the first subimage declaration has a picture of an apple and the second subimage declaration has a picture of a gem, the sprite's animation will be an apple followed by a gem. Do keep in mind that all animations cycle (meaning that, when the animation reaches the end, it will start again from the first subimage).

Next, we'll learn how to create the item!

Creating an Item

Item Definition

In Dungeon Souls, items are objects that aid the player during runs. These can behave in one of three ways:

  • Passive Effect: stat increases or special actions that occur during specific events;
  • Active Effect: stat increases or special actions that occur when the player triggers its active item ability. The player can only have one active item at a time;
  • Consumable Effect: stat increases or other usually less significant actions that occur when the player consumes the item (triggering its active item ability). Consumable items can stack.
Items are found in chests and can sometimes be dropped by monsters and other special items. Depending on their rarity (denoted by their outline color), items can only spawn in certain chests. Usually, the rarer the item is, the more powerful the effect is. Each item can have one of the following rarities:
  • Common: common items with normal/simple effects, spawning frequently in regular chests;
  • Uncommon: these are less common than Common items, but can still spawn in regular chests;
  • Rare: items that are rare to spawn, whose effects are quite powerful. These usually spawn in higher-end chests;
  • Legendary: items with dramatic effects, spawning only in the rarest chests (or occasionally on the shop keeper);
  • Cursed: items that usually give an over-powered effect, but have a terrible downside. These items only spawn in Cursed Chests
An item can be defined in one of the following ways:
  1. Right-click on the target project, then select Create New...>Item
  2. Selecting the desired target project on the Project Tree, then clicking the quick action New Item
After a request to create a sprite has been performed, a window will appear, asking the following set of information:
  • Name (required) - The name of the item, which will be shown in-game.
  • Description (required)- The description of the item that will be shown during game.
  • Max Stacks (required) - The maximum amount of times the item stacks (a.k.a. the amount of times the player can pick up the same item).
  • Price (required) - The base price of the item (when sold in shop). The price scales with the dungeon's level.
  • Type (required) - The type of item.
  • Rarity (required) - The rarity of the item.
  • onItemPickup Function - The function to call when picking up the item.
  • onItemActivated Function - The function to call when the player activates the item (only for active and consumable items).
  • stepTick Function - The function to call at each step (only for passive items).
In this tutorial, we'll fill the information as follows:
  • Name (required) - Life Alchemy Gem.
  • Description (required)- [ACTIVE] Kills all nearby enemies and#converts their deaths into max hp
  • Max Stacks (required) - 1
  • Price (required) - 600
  • Type (required) - item_active
  • Rarity (required) - item_legendary
  • onItemActivated Function - lifeAlchemyGemActiveFunction()

After filling the information, the editor should generate the following code:
define item itm_Life_Alchemy_Gem
{
[NAME]="Life Alchemy Gem";
[MAX_STACKS]=1;
[DESCRIPTION]="[ACTIVE] Kills all nearby enemies and#converts their deaths into max hp";
//[SPRITE];
[TYPE]="item_active";
[RARITY]="item_legendary";
//[ON_PICKUP_FUNCTION];
[ON_ACTIVE_FUNCTION]=lifeAlchemyGemActiveFunction();
//[ON_STEP_FUNCTION];


//Place function declarations here...
}
Despite the editor generating a lot of the code for the item, we still need to do a few changes:
  • Set the sprite of the item (to which we just need to set it as sprLifeAlchemyGem, as defined previously);
  • Define the lifeAlchemyGemActiveFunction that will be executed when the player activates the item.

It is important to note that, due to the way Game Maker Studio 1.4 (Dungeon Souls' native game engine), the special character # seen in the item's description corresponds to the new line operator, creating a new line at the specified place. For example, the example "This is a line#This is another line" would produce the following text:

This is a line
This is another line

Function definition is slightly more complicated (since it will be the first time we also introduce coding concepts). As such, the next subsection will provide a bit more information of the function that we need to define in our Life Alchemy Gem item.

Function Definition

In DSML, functions are, in a nutshell, blocks of code that can be called by other blocks of code. The difference between regular blocks of code and functions is that functions can accept input values (known as arguments), as well as produce an output value (known as the function's return value). Functions are useful for a wide variety of reasons, since they allow for code to be reused.

One of the possible uses is to keep the code modular and easy to understand. Let's briefly explore this concept in a simple example. Let's assume that we're making a piece of code where we need to use the total defense of the player several times. We'll also assume, for now, that the player only has defense and magic resist. As such, the code would look something like this:

...
defensePercentage=defense/(defense+resist);
resistPercentage=resist/(defense+resist);

...

In the example above, we're calculating what percentage does the 'defense' stat occupy from the player's total defense and doing the exact same thing for the 'resist' stat. At a first glance, this bit of code might seem okay, but shortly becomes a problem when we ask ourselves a simple question: What if the total defense of the player changes over time?

For example, it's perfectly plausible to think that the total defense of the player might include bonus defense or bonus resist (or some other variable that we do not know yet). If this happens, this means we'll have to change all lines of code that calculate the total player's defense. This is where functions come in handy. If we had a function that would return the total defense of the player, then we would only need to make changes in one place! Such a function could be defined as follows:

define function totalPlayerDefense
{
functionOutput result;
//...
result=defense+resist;
}

And done! With the function above, we would replace our existing code with the following:

...
defensePercentage=defense/totalPlayerDefense();
resistPercentage=resist/totalPlayerDefense();

...

As we can see, not only is it easier make any changes to the total player defense behaviour, as it is also easier to know what our code is doing! Even though this is a small example, keeping this kind of mindset is highly encouraged and recommended, as it will make your life easier in the future.

Now, back to the definition of our lifeAlchemyGemActiveFunction! Remember that our item is supposed to kill all nearby enemies and then increase the maximum health of the player by the amount of enemies killed. This effect can be composed into smaller tasks that make it easier for us to code the behaviour:

  • Start the enemy kill count with the value zero;
  • Get all enemies within a certain radius;
  • For each enemy found:
    • Kill the enemy;
    • Add 1 to the enemy kill count;
  • Get the player's maximum health;
  • Add the kill count value to the player's maximum health;
  • Set the player's new maximum health;

Now that we've described the foundations of our code into simple tasks, it's time to translate these tasks into something the machine can understand (in this case, translate it into DSML)! We can do this as follows (feel free to take your time analyzing the code):

define function lifeAlchemyGemActiveFunction
{
//We'll start by getting a list of all the enemies in a 96 radius of the player
playerX = getPlayerAttribute("x");
playerY = getPlayerAttribute("y");
enemyList = enemiesInRadius(playerX,playerY,96); //enemiesInRadius returns an id to a list that contains all enemies found in a specific radius.
enemyCount = 0;
//Start the variable that will count how many enemies we've killed. Now, we'll cycle through it using a for statement.
size = listSize(enemyList);
for (i=0:i<size;i=i+1)
{
enemy = listGet(enemyList,i); //Get the id of the enemy
enemyKill(enemy);
enemyCount = enemyCount+1; //Count one more enemy killed
}
maxHp = getPlayerAttribute("hpmax");
maxHp = maxHp+enemyCount; //Add the amount of enemies killed to the player's max hp
setPlayerAttribute("hpmax",maxHp); //Set the player's max hp
listDestroy(enemyList); //Every list allocates memory in the game. When we no longer need it, we destroy it!
}

If you have any questions about the code presented previously, you should visit the reference links that explain what each function does. Apart from the function, the only more complex piece of code is the for statement. This statement is used when you intend to cycle a fixed amount of times and you need to know which iteration you're currently at. A translation of the for statement is as follows:

for (i=0 <start by setting a value to a variable>;
i<10 <keep running the code after the for statement until this condition is false>;
i=i+1 <at the end of each iteration, execute this block of code>)
{<the block of code to execute at each iteration. The variable initialized will hold the value of the current iteration (in this case, the variable i)>}

And that's it! Our item is finally complete! You can now test your item by pressing the Play Button. It will load the game in a special room where you can test your mods at ease. Pressing the F2 key will allow you to spawn the items of your module. Next, we'll learn how to publish your module!

Publishing the Module

Dungeon Souls allows the addition of modules that are external to Steam Workshop. If you intend to publish your project to Steam, please visit the Steam Workshop documentation page for more information. The upside of having the Dungeon Souls on Steam is that the game automatically adds the subscribed modules.

In order to add the module to the game without the help of Steam, we need to do the following steps:

  1. Compile and build the module by selecting Compile>Compile Module for Release (CTRL+R).
  2. Extract the .zip onto a folder (name the folder the same name as the module, like, for example, XPTO)
  3. Locate the game's save folder.
    • On Windows, it's %localappdata%/DungeonSouls. Typing this on the file explorer's search bar will take you to the directory
    • On Ubuntu (Linux), it's /home/<username>/.config/DungeonSouls
    • On Mac OS, it's usually ~/Library/Application Support/DungeonSouls
  4. Create a folder and name it Mods.
  5. Inside the Mods folder, create a file called mods_location.config.
  6. Copy the contents of the extracted module onto the Mods folder
  7. Add an entry to the mods_location.config file containing the relative path to the module file (dsa). For example, if we've added a folder named XPTO with a module file inside called XPTO.dsa, the entry would be XPTO/XPTO.dsa

To share your module outside of Steam Workshop, you'll have to distribute the Zip file yourself.

... And that's it! This completes the tutorial on How to Create Your First Module! Checking out the rest of the documentation, as well as the Quick Onboarding is strongly recommended! We hope you have fun creating modules for the game and feel free to contact us at any time!