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.
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:
For the sake of this tutorial, we'll input the following information into each field:
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.
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:
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.
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:
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:
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!
In Dungeon Souls, items are objects that aid the player during runs. These can behave in one of three ways:
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:
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.
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!
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:
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!