Welcome to the first project update for Dicecord. This is a diceroller/digital character sheet for use with Discord. Its UI is built in PyQt5. This goes over the Dicecord-lite version, shows a preview of the character sheet display and generally talks about how the coding experience has been. This is going to be a big update, because it is the first and it covers multiple weeks of work. Going forward I am going to try and do one of these every 1-2 weeks, just to summarise the work I’ve done and get my plans for the next weekend in order.
The project started a few weeks back when a friend of mine expressed interest in running a Mage: The Awakening RPG over Discord. I was just after finishing a python refresher on coursera and decided to use this as an opportunity to build a bot, which you can find on my github here. You type commands and he rolls your dice, following Chronicles of Darkness rules. (roll a number of D10s, 8 or above is a success, 10s reroll, count number of successes) He’s pretty snazzy.
Coming to the end of that project I felt like having to type all the time would be a bit of a pain and started looking at ways to move this from a bot to a standalone client. Not only would this be a bit more user friendly, but it would open up things like character sheet display, tracking health, recording xp and eventually building the dicepools for you based on your characters stats/status. It went through a few names but in the end I settled on calling it “Dicecord” because my sense of humour is terrible. Here’s how it looks:
This is a feature complete version of what I am calling “Dicecord-lite”. It has none of the character sheet related interactions, but allows for rolling dice and having the results posted in a Discord channel. When you open the client you first have to enter a Discord Webhook URL and some sort of User ID. You add it by hitting the option in the “Import” drop down menu. Once that is done, you simply enter in the number of dice you want to roll, change some attributes of the roll if needed and push the button. The client will make the roll for you and then start trickling in results to the appropriate Discord channel. If a numerical Discord ID was entered previously, each result will include an @mention so you can easily see your rolls in a busy channel.
To both add drama and help avoid hitting a rate limit, the client will wait one second between sending each die result, but it will also monitor headers to check if approaching a rate limit and add extra delays if needed. If you hate fun, you can activate Quiet Mode and just get your total successes posted to the channel straight away. The bottom bar in the client will always display the last roll’s total successes and you can use the “Show Last Roll” option in the “File” menu to display your entire last roll, even if quiet mode was chosen.
The code consists of two files. The first, called player.py defines a general “Character” object. This object holds the last roll made and some stats of the character. This also contains methods for making rolls which output as lists of strings. This makes it a very general purpose object that is independent of the means used to display the roll results. It also has methods for other interactions which I might or might not implement in the full version of Dicecord. The second file, Dicecord.py contains the UI for the client and also a function for posting messages returned by the character methods to a Discord channel.
The UI was built using PyQt5, which is a python package created by Riverbank software to make Qt UI via python code. You can also use the Qt designer to visually build the UI and write python code for it, but I found that its code is a tad messy and can have formatting issues, so I just write the code myself now. Initially I built it in TKinter, the “default” UI package for python, but I found it was a little ugly and wanted something a bit nicer. One advantage of PyQt is that you can set the UI using CSS stylesheets and have the script read from it, which is something I may end up doing for character sheet display.
One big disadvantage is that the PyQt package is quite huge, which is quite important when it comes to distributing the client. At work I usually use pyinstaller to freeze my scripts into exes, which basically turns them into a self-extracting zip that includes my entire python library and PyQt makes my library pretty big. A onefile pyinstaller freeze for the TKinter version was ~7MB but with PyQt we’re looking at 30+MB. For distribution, my plan is to use pyinstaller to freeze the script and then use NSIS to build an installer which includes the ondir freeze as well as folders contain any other media used by the app. This will also give a bit of a more professional first impression to users, along with giving them desktop and start menu shortcuts.
On top of that, I am also working on character sheet display for the tool. The full version of Dicecord will have you build or import a character and then give you the option of displaying its character sheet. It will eventually construct pools by allowing you to highlight the stats you are rolling on directly from the sheet. This sheet is only partially complete and only displays stats relevant to modern day mage right now, here is what I have so far:
This is not just cosmetic but pretty functional at this time. When you click the “edit” button it activates edit mode, which allows you to change content on the sheet. In edit mode you can write in the boxes up top, you can click dots to change your rating and you can add/remove merits. Each change you make will update a character object in the back end that stores all the stats; the same character object that contains methods for doing rolls in Dicecord-lite. Any derived stats are also recalculated when a change is made.
The test button currently prints the current character stats and allows me to make sure that changes are being saved correctly. Currently it makes a blank character using global variables defined in stats.py, but eventually you will have the option to import a file representing a character and prompted to overwrite or save a new file if changes are made.
This UI is a pretty deep chain of widgets within widgets. The dots are abstract buttons which can be in a filled or unfilled state; each row is a Stat object containing the label, dots and a dot button group which detects clicks and changes which dots are filled if edit mode is active; then each block of stats uses loops and dicts to check which stats they are supposed to draw; the blocks are organised in grids by the overall sheet widget.
Making the system to add new merits to the character sheet is probably the hardest thing I have had to do so far. In order to achieve this I had to have the merits object track the current last row and then move around its widgets when changes are made. I was going to allow for the editing of a name of a merit but that ended up being much more complex, so for now all we have is add and remove. One seemingly weird thing is that the attribute in the merits object that counts the last row doesn’t change when rows are removed, however PyQt is smart enough to ignore rows that contain no widget when it draws the UI, so everything still lines up.
The biggest disadvantage of PyQt is that it will force close the client when any error happens without displaying a traceback. This has been quite frustrating as I am left only have a very general idea of where the error could be and no real context of what went wrong! Did I typo a variable call? Does this Qt object not use that method? Does the Qt method expect a different input? Who knows?! There are apparently ways to get a traceback printed to console but I haven’t looked much into that side of things yet. Luckily, the Object Orientated approach I am taking means that I am working on small chunks at a time so its easy to narrow down and fix errors before adding the widget to the whole sheet. It has also served as a great introduction to PyQt since searching the Qt docs for possible causes allowed me to find useful methods outside of what the standard tutorials go over.
So, there we have the current update. The code can be found here on my gitub. My plans for next weekend:
- Make a “Square” object. This will be an abstract button that can be unfilled, contain a diagonal line, contain an x or be filled completely. In most cases it will switch between filled and unfilled, but for health it will go through all stages.
- Add a rote box to skills column, which is used to flag skills favored for rotes by the character. For now it will be manually edited, one day it might fill based on Order membership.
- Add a widget to display derived stats, xp and beats at the bottom of the middle column. The derived stats will update whenever any other stat is changed but xp, beats and armour can be changed manually at all times.
- Make a big circle option for the dot abstract button that will be used for Wisdom, Gnosis and Willpower.
- Add in Wisdom, Gnosis and Willpower.
- Find some way to allow setting a skill rating back to 0.
- Get started on Health and Mana display.