dnd-discord

Dungeons & Dragons

I've always wanted to play through a campaign but never found the time to dedicate to it. I thought it would be interesting to integrate D&D with Discord. There are many bots that do this to some extent, but I had some ideas that would hopefully really help immerse players in the campaign.


These were the requirements I had for this new system:


Implementation

Role Representation

In theory this was pretty straightforward, each attribute was defined as a role that would be updated when needed. After a few refactorings, I settled on dividing the different attributes into Tokens and Labels.

Tokens would have a numerical value attached, and would be used for dynamic attributes like HP, levels, status effects, and cooldowns.

Labels would be a static piece of text that could be used for setting player classes, but it could also be used for specific status effects that didn't need a number attached.

// tokens.json
{
    "hp": {
        "template": "$VAL HP 💗",
        "min": 0,
        "max": 50,
        "find": "💗",
        "hideEmpty": false,
    "color": "RED"
    },
    "mp": {
        "template": "$VAL MP 🔷",
        "min": 0,
        "max": 50,
        "find": "🔷",
        "hideEmpty": false,
    "color": "BLUE"
    }
}

(I setup a JSON for the tokens, the template would be filled out with functions later on)



After I setup the structure of the JSON, I got to work on coding the backend. The discord.js library was easy to use and the resulting code was short (< 50 lines).

Now for the problem of how it will be represented on Discord. There were two choices: have the token be edited on the fly, or pre-create all the token values and swap between them as needed.

I ended up picking the latter because the first option was far too API intensive and I kept getting rate-limited :(



This would have been a huge pain to manually create each time I wanted to add a token with many different values, so I added a function to the backend that creates each role (checking if it exists first) for me.

While I was at it, I also created a teardown function that removes all token roles from the server.

Label tokens didn't have a representation in the JSON file, they were simply created on the spot. I didn't think it'd be a big problem since labels wouldn't be updated as frequently as tokens.

Channel Representation


I figured having channels be locations would make the most sense, it might even lead to interesting situations where some members of a party are in different channels than other members, so they literally can't see what's happening and have to rely on other players describing what they see.

Setting this up took some attempts but I ended up with a solution that sets custom view permissions for each member, for each channel.


Simulating NPCs

Finding the right solution

This was probably the hardest problem to solve. So far all I had was channels and roles being edited, which many other bots can do in one way or another. To make this project unique, I wanted to help the Dungeon Master simulate NPCs through discord.

I began looking at options on how to do this as efficiently as possible, starting by googling the concept in a general sense. I found a bot called Tupperbox that lets you use webhooks to roleplay characters (right pic).


At first this seemed perfect except when I clicked it and realized that webhooks couldn't be given roles. This was a problem. The whole Token and Label system revolved around being able to add and edit roles, and I didn't want to compromise on that, so I ended up dropping the easy way out and choosing to use Shells instead.


Shells

I figured that the best way to simulate an NPC would be to create a whole new bot account and have the main program manage a list of these NPC bots accordingly. I ended up referring to these NPC bot accounts as Shells.

Hopefully this isn't a violation of Discord TOS but it DID let me create multiple bot accounts and use them in the same script so it should be fine.

// setup shells
var shellKeys = JSON.parse(fs.readFileSync("shells.json"));
var shells = [];
for (var i=0; i!=shellKeys.length; i++) {
    var s = new Discord.Client();
    s.login(shellKeys[i]);
    shells.push(s);
}

I have a list of client keys in a JSON file that are used to login a list of discord clients. Effectively this lets me control any number of bots at once, with handy functions that let me change their nicknames and profile pictures at will, allowing any roleplay scenario to be constructed.


Frontend

When the question came up on how the Dungeon Master was going to control all of these features, a text-based UI was out of the question. Typing is far too slow for manual commands, so a GUI had to be made. I regret deleting my GUI drafts, they would have been excellent filler images to use in this part. I could talk a lot about the GUI problems I ran into but it would mostly be CSS being obnoxious, so we'll skip that and see the final result.

Each player is assigned a userController box that lets the Dungeon Master edit attributes about them. Channel view permissions can be toggled by a single button click on the right side. The left side is for Tokens and Labels, the former having increment buttons to change the respective value (you can also click the number to edit it directly). Roles can be added or deleted easily using the top bar. Once changes are made, the Send Changes button can be pressed to send the change packet to the backend, where it will be parsed and sent to discord. Changes will appear in realtime.

The section below that is for NPC controlling. The Dungeon Master can type a message, choose a Shell and a channel, and then hit SEND. The message will then popup in the chosen discord channel, sent from the selected Shell.


Fluff


After testing a demo campaign, the biggest problem was that dice rolls had to be done externally, which messed with the immersion. So I quickly coded a dice rolling parser to help tie things together.

Pitfalls

Definitely the biggest problem I had was the Discord API being too slow. I was averaging around 3-5 seconds for an update affecting 2 party members, and as high as 10+ seconds when the API is particularly laggy. I optimized this down from 15-20 seconds per update, by grouping requests into batches that would be executed all at once, instead of an API call for every member role change.


Download

If you would like to try using this program, here is the link to the repository. You'll need to create several bot accounts, which can be done on the Discord developer portal. Secret files have been omitted from the repo, so you'll need to create two files and drop them in the folder

// secret.json
{
    "APIKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "hostId": "00000000000000",
    "guildId": "000000000000000000000"
}
// shells.json
[
    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
    "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
]

If you run into any problems setting up or using dnd-discord, feel free to email me for help.