Local

All work

P/02

DnD Friends

A Discord bot that runs the D&D Friends server: characters, inventories, quest timers, a shop, guilds, and mod-approved session logs.

DnD Friends wordmark in white hand-lettering beside a smiling yellow twenty-sided die on a dark background

The D&D Friends Discord server runs a play-by-Discord economy: characters earn rewards for quests, spend them in a shop, craft magic items, and join guilds. Tracking all of that by hand stopped scaling past a handful of players.

friendbot is a Python bot on discord.py with MongoDB behind it. DMs prepare a quest timer, players sign up with a character and consumables, and when the timer stops the bot works out rewards for everyone, including tier crossovers and multipliers. Session logs go to mods, who approve or deny them with reactions. Item tables, the shop, guilds, campaigns, and admin tools each live in their own cog, so adding a feature is dropping in a file.

It shipped on Heroku and ran the server for years. Development has since moved to a community-maintained fork. The write-up below covers how it grew from a session timer into a database-backed character system.

Case study

01 Intro: history, dungeons, and dragons

This case study may not be perfectly accurate since I no longer maintain the project. Most of it is recollected from what I did back then. :)

Back when I was a wee lad in university, I was very interested in an open-ended role-playing game called Dungeons and Dragons: a storytelling game where one player is the narrator (the dungeon master) while the other players each control a character in a fictional setting. I joined a D&D club, but meeting once a week was not enough to support my unhealthy obsession. After some research, I looked high and low until I found a Discord server to play D&D in, and boy… did I fall into a deep, deep rabbit hole.

The D&D Friends landing page: the headline hey friend, a pitch for a D&D 5e one-shot server with 3000+ users, a Join now button, and a large smiling yellow twenty-sided die
The server's landing page

02 So what's the problem?

This Discord server doesn't play traditional D&D, where a full campaign spans many meetups and sessions. It follows a format similar to Adventurers League, where people can sign up for a one-and-done session of D&D (usually called a one-shot). The neat thing is that once you create a character, you're able to reuse and level them up through different one-shots and keep playing. You would then fill out logs and have the server's moderators cross-check to make sure everything was correct.

After playing for about a year and becoming a moderator on the server, I thought to myself (as any smart programmer would, lol): why not automate this?

03 3… 2… 1… Go!

I decided to jump in and make a Discord bot with Python (a language I was comfortable with at the time) and start small, you know, dip my toes in the water. When you play D&D you accumulate experience (needed to level your characters) based on the amount of time you play in a session. So my first goal was to create a timer to keep track of time. How hard could that be? Well, it wasn't too hard to create a basic timer. I started seeing value in it and began exploring the potential for different types of automation. What if we could automatically create characters and store them in a database? Provide item table lookups? The possibilities were endless…

04 Database

Utilizing a database might have been the biggest turning point, as it paved the way for connecting all of the bot's features. Here I was able to store data for lookups (items, classes, races, and so on) and player tracking (characters, character inventories, and more). This basically eliminated all of the manual tracking and user error for players, so they could focus on playing rather than logging.

Fun fact: the first "database" was a Google Sheet. The magic item tables and the character records all lived in spreadsheets that the bot read through the Sheets API, which worked right up until the bot outgrew it. Moving everything into MongoDB was the real turning point, and you can still find the old spreadsheet code commented out in the repo like a fossil.

Diagram titled Discord Stat Tracking: a Discord server links to server stats, Discord users, and player characters with inventory, guild, level, and stats; below, item lookup boxes for items and shop, classes, races, and feats, and guilds
Rough diagram of the bot's data

05 Tying it all together

And finally, I tied all the functions and the database together. To better explain it, I'll walk you through how you would use the bot as a new user. As you read through the flow, you may notice that most of it relies heavily on the players and how they use the bot.

Creating a character

  1. Join the server and go to a bot command channel.
  2. Create a character with the $create command. It pulls classes, races, and other options from dictionaries, walks you through a series of prompts you answer with Discord reactions, and calculates and allocates all stats automatically per D&D rules.
  3. Then the player is ready to play!

Joining a game

  1. In the quest channel, look for games to join if there are spots open. Players can also create games through the bot if they're a DM or narrating.
  2. When the day comes to play, the DM uses the bot to set a timer for the game and keep track of how long it runs.
  3. During the game, players can run timer commands and use their items through the bot, and the DM can hand out rewards.
  4. After the session is over, the DM stops the game, and rewards and XP are automatically allocated to each player. No more manual logging!

Afterword

The above covers the main use of the bot. Outside of games, you can use it to manage characters, items, guilds, and many other things. Information lookup is also a nice feature. Here is an example of a character lookup in action!

A Discord screenshot: a user types $i keo and D&D Bot Friend replies with a character card for a level 18 Goliath Barbarian listing one-shots played, gold, tier points, guild rank, feats, attuned items, and ability scores
Character lookup of a specific character, with some details blurred out

06 Show me the loot

Rewards are where the bot earned its keep. The server hands out CP (its stand-in for experience) based on time played: every 30 minutes is worth half a CP, so a four-hour one-shot nets 4 CP. Each CP also pays out gold and Treasure Points depending on the character's tier, and the payouts climb as you climb. Tier 1 pays 50 gp and half a TP per CP, while Tier 5 pays 200 gp and a full TP.

The fun part is the edge cases. If a character crosses into a new tier mid-session, the CP earned after the crossover gets paid at the new tier's rates, so the bot has to split the session at the threshold. Bonuses stack too: a guild bonus, a player bonus, and a DM bonus each add another full share of rewards, and there's a separate path for characters who die during the session (sorry). Doing this by hand was exactly the kind of thing that used to go wrong, so the bot does it the same way every time and even lets you preview it with $reward before the game.

07 Mods have feelings too

Remember the part where mods cross-checked every log by hand? That became a workflow instead of a chore. When a DM stops the timer, the bot writes a session log with everyone's rewards and posts it for review. Mods approve or deny with a reaction, and if only part of it is off, they can permit or deny just the guild credit, the rewards, or the items instead of throwing the whole log out. Players can even opt out of a log if they need to.

On approval the bot applies everything to the characters and messages each player directly to say their rewards have landed. On denial, everything is cleared. Nobody has to edit a spreadsheet at midnight anymore.

08 Shop 'til you drop

Once gold and TP were flowing, they needed somewhere to go. The shop lets characters buy and sell mundane items and spell scrolls (and copy a scroll into a spellbook if you're the wizardly type), and spend downtime training a language or a tool proficiency. Treasure Points work like a layaway plan for magic items: you put TP toward an item from the tables, finish it off later, or pay the difference in gold and get your TP refunded. You can also discard an unfinished item, or abandon leftover TP in a tier you've outgrown.

Guilds round it out. Players create and fund a guild with gold (200 gp at low levels, up to 800 gp at high levels), join up, and rank through the server's Noodle ranks. DMs can attach up to three guilds to a quest, and a guild's reputation can buy quest buffs like double rewards or an extra item drop. Yes, the ranks are Elite, True, Ascended, and Immortal Noodle. No, I will not be explaining the noodle thing.

09 Under the hood

A few things I'm glad I did early. Every feature lives in its own discord.py cog, and the bot loads whatever is in the cogs folder on startup, so adding a feature was dropping in a file (and a $reload command meant I didn't have to restart the bot to ship it). The whole UI runs on reactions instead of more commands: the help menu pages with number emojis, the item tables paginate the same way, and every purchase or destructive action asks for a ✅ before it does anything.

A few things I learned the hard way. Discord hands you a raw string, so half the work is parsing quotes and commas. The bot has a global error handler that turns missing-quote mistakes, cooldowns, and unknown commands into friendly messages, and anything it doesn't recognize gets DM'd to me with a traceback. Nothing teaches you defensive programming like your phone buzzing because someone typed a curly quote. It all ran as a worker on Heroku on Python 3.7 with MongoDB behind it, and the character cog alone grew to about 4,000 lines. Not my proudest architecture, but it did the job.

10 So long, friend

By the time I stepped away, the server was 3,000+ people strong, the bot had grown to roughly 500 commits across a full 2.0 rewrite, and it had become a team effort, with friends from the server contributing fixes and features alongside me.

Unfortunately, I fell out of love with D&D as I got busier and no longer enjoyed the 4+ hour commitment per session. However, I learned a lot from this project, which I developed over 2+ years. I burnt out and transferred maintenance to a friend who still manages it to this day.

Year
2019
Type
Discord bot
Role
Solo build
Stack
Python / discord.py / MongoDB / Heroku

Read 0%