Roll20 character sheet import
Intro
A long term project of mine was to find an easy way to import character sheets into Roll20.
There are tools for D&D 5e and such but nothing for my game of choice, Astonishing Swordsmen and Sorcerers of Hyperborea. Hyperborea for short.
Recently I did find an API using tool that would work. However, that requires you to have a paid account. Which I do. I still might use it.
However, I did find something that mostly works, Roll20 Character Import/Export.
I do have a program that can take a yaml file and populate a form fillable pdf. Taking that and banging my head against the wall for a few weeks. With some help from a friend to understand some javascript I was able to get it working as much I really need it to.
The Base Character Sheet
So, I'm trying out the idea of keeping character info a yaml or markdown file.
I've settled on something like this:
ST: 17
DX: 10
CN: 14
IN: 17
WS: 12
CH: 12
class: fire lord
level: 5
Race: Kelt
Gender: female
Age: 17
HP: 45
XP: 45,000
Gear:
- <starter_pack>
- Hunting horn
- leather mask
- <hm>dagger x3[w]
- <a>small shield
Magic:
- Hand Axe +2
Spells:
- Flaming Missile
- Flame Blade
- Fireball
This has some syntax in it that I built up as I was making my pc generator for Hyperborea. There are many table lookups and such. I then dump the data into a Form Data Forma, (FDF) that I can use to create a Hyperborea character sheet.
I can take data from one format and munge into another.
Now to think about the next data format.
Roll20 Character Sheet
Roll20 has a lot of character sheets you can use in your game. A lot of them, you can even submit yours to be added to the repo.
To figure out what Roll20 has named each of the various attributes you will need to get a copy of your target character sheet. You can either clone their repo or you can find your character sheet in the repo and get that copy.
For HTML munging and extraction I reach for Mojo::DOM.
It doesn't take much to get what I want:
my ($html) = join('',<>);
my $dom = Mojo::DOM->new($html);
say $dom->find('[name]')->map(attr => 'name')->join("\n");
It's just using CSS selectors so it shouldn't be too hard to translate that to the language of your choice.
I then created a mapping to go from my program (centered around an FDF) to the roll20 names:
"Character Name": character_name
"Class": class
Level: level
Align: align
ST: ST
"ST Attack Mod": meleehitbonus
"ST Damage adj": meleedmgbonus
"Test of ST": testst
"Feat of ST": featst
Next is the hard part.
Import Character data into Roll20 Character Sheet
I did find something that was relatively easy...ish to import that data, ChatSetAttr.
However, most folks don't pay for roll20. I use it enough and I like to pay for useful things like it, but I still wanted a way to get character sheet data imported.
To be honest...I was looking at the problem off and on for probably a year before I found this Firefox plugin.
I have manually entered the character data in a test campaign I use for ideas I want to try out. I mention this because my initial attempts to import a character that I hadn't manually imported, nor used the ChatSetAttr API didn't work.
Eventually I hit upon exporting a character sheet that I had filled out manually. This gave me the format to import.
{
"schema_version": 1,
"name": "barb_test",
"avatar": "",
"bio": "",
"attribs": [
{
"name": "reactionmod",
"current": "1",
"max": "",
"id": "-M4dusSQdUOIdxhCvhKD"
},
I was then stuck on that id.
I went to the roll20 forums and was pointed to some code.
This code creates a sort of UUID. The main thrust of it being something like:
return function() {
var c = (new Date()).getTime() + 0, d = c === a;
a = c;
for (var e = new Array(8), f = 7; 0 <= f; f--) {
e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64);
c = Math.floor(c / 64);
}
A revelation
So, now I needed to translate those functions into my weapon of choice, Perl.
A friend helped me with the translation so I could generate uuid's in the format that roll20 understands.
That bit of help broke open the damn...or...was just enough help to smash my head through the wall I was hitting against.
The repeating items problem
Being able to import basic player info was great.
Now I had to tackle what are in the html as repeating items. Things like gear, abilities, weapon info, spells, etc.
This was mostly a puzzle for my code. The uuid code does have something explicit for repeating items uuids.
{
"name": "repeating_melee_-M4dusTPpXLZyuX3dipz_meleeweaponname",
"current": "dagger",
"max": "",
"id": "-M4dusTP3-Ipr2NBQx-P"
},
{
"name": "repeating_melee_-M4dusTPpXLZyuX3dipz_meleeclass",
"current": "1",
"max": "",
"id": "-M4dusTQdb3JG38lovQM"
},
{
"name": "repeating_melee_-M4dusTPpXLZyuX3dipz_meleeatkrate",
"current": "1/1",
"max": "",
"id": "-M4dusTQEEylJV5QjKro"
},
{
"name": "repeating_melee_-M4dusTPpXLZyuX3dipz_meleeatkmod",
"current": "0",
"max": "",
"id": "-M4dusTQ7yLYX5ddp6oD"
},
{
"name": "repeating_melee_-M4dusTPpXLZyuX3dipz_meleedamage",
"current": "1d4+1",
"max": "",
"id": "-M4dusTRgK3BjoBRFvle"
},
In the above snippet, this represents the info for a melee weapon. It's name, weapon close, attack rate, attack modifiers, and damage. There is a a field for notes which I didn't enter for this weapon.
Each html attribute name is of the format, "repeating_melee_ROWID_attrname". ROWID is replaced with a row id that is similar to the "normal" uuid. Each item in the same line has the same ROWID but different "id"s.
That is basically it.
I am still going through and adding things but now it's just an iterative process. I update the code, fix the syntax errors in my code because I mostly do this at the end of the day and I have very little brain for such task, and then do and import to see what it looks like.
So far the only thing that isn't really working is the equipment list. I'm ok with that. It can be added manually if needed. These programs take a lot of the pain out of this.
TL;DR
- start with a beginning data format
- munge to a target format
- add another target format
- extract HTML attribute names from an html file
- use those names to map from initial format to new target format
- create properly formatted json file
- test import
- check that it looks ok
- lather
- rinse
- repeat as needed
Fin
And with that...I need to go to bed, maybe watch some WestWorld. Good night y'all.