The game Sorcerer has an interesting dice mechanic and turn sequence, but without the ability to make opposed rolls, and to see the rolls everyone in a turn has made at once, it is very clunky.
For this post, we’ll just look at building the roll. In the next post, we’ll look at the more complicated as[elements needed to properly represent Sorcerer.
The Sorcerer Dice Mechanic
The basic idea is simple. Roll a number of dice equal to your score, and order them from high to low.
That is easily represented in roll20 with something like /roll (@{ability}+@{modifier})>1sd.
That gets the basic roll. But now when you compare two or more rolls, you start by comparing the highest die. If they match, then go on to the next.
It’s far from ideal, but it can be done. But, now imagine 6 characters all rolling dice, and you have to compare them. And so on, until you have an order from high to low. This can be a pain.
Luckily, Custom Roll Parsing gives us a handy solution. It might still be a little clunky, but it is much easier. First, lets see how to make default rolls using CRP.
Describing The Task
In Sorcerer, you pick a stat which has a score usually from 1-5, then the GM awards bonuses, and you roll one die per point in the stat. The actual size of die can vary, but most games use d10s.
Order your dice from high to low, then compare against an opponent. Look for your highest die, then count how many dice on the opposing side beat that high. If it’s zero, you lose. The winner will have some number, and there is always a winner.
So if you roll 7, 5, 3 and your opponent rolls 1,2,3,4, their best die is 4, and two of your dice beat it so you win with a degree of two.
If the top dice on each roll match, you remove them, so 8, 6, 4 vs 8, 8, 8 becomes 6, 4, vs 8, 8, and the second pool gets 2 victories (The two 8’s beat your best die which is a 6.)
For today’s post, though, most of that is ignored. We’ll simply be rolling a number of dice and ordering them from high to low. The comparison is handled later.
Designing the Roll
There are several steps needed by all Custom Roll Parsing creations:
- The HTML to show the original stats and/or dice rolls
- The Sheet Worker, which responds to the HTML, extracts data from there, makes a roll, then sends the final result to the rolltemplate.
- The Roll template, which needs both HTML and CSS.
In the simplest CRP examples, you can use the default
rolltemplate and skip that last step (and it’s very handy when you can do that!), but we can’t do that here.
In Sorcerer, you only have a handful of stats. For this system, you need a button named for each stat, and a select
or input
of the same name (that naming is very important), containing the stat value. Here’s what one of those stats might look like.
<button type="action" name="act_lore">Lore</button>
<select name="attr_lore">
<option selected>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
Code language: HTML, XML (xml)
We have a button to roll the dice, and a select to store the stat value. Notice both are named the same.
The we have the CRP sheet worker. I’ll explain why it’s written the way it is below.
<script type="text/worker">
const skills_sorcerer = ['Stamina', 'Will', 'Lore', 'Cover', 'Humanity'];
skills_sorcerer.forEach(skill => {
on(`clicked:${skill.toLowerCase()}`, () => {
const die_calc = die => String.fromCharCode(96 + die);
getAttrs([skill], values => {
const skill_score = +values[skill] || 0;
const roll_string = `&{template:sorcerer} {{title=@{character_name} rolls ${skill} }} {{skill= ${skill} }} {{score=[[${skill_score}}]] }} {{modifier=[[?{Modifier|0}]]}} {{dice_roll=[[ [[{(${skill_score}+(?{Modifier})),1}kh1]]d10]]}}`;
startRoll(roll_string, sorcerer => {
const dice = sorcerer.results.dice_roll.dice;
const modifier = sorcerer.results.modifier.result;
const sorted = dice.sort((a,b) => b-a);
const print_out = sorted.map(a => die_calc(a)).join('');
finishRoll(sorcerer.rollId, {
skill: skill_score,
dice_roll: print_out
});
});
});
});
});
</script>
Code language: JavaScript (javascript)
We need an array of all the possible skills. Then the forEach
loop goes through them and creates a sheet worker for each of them. This will cope with dice pools of any size.
Because we are using a forEach
loop, when the sheet worker is triggered, we know which skill is being clicked – that is inside the skill
variable.
The roll_string
variable is created on the line before startRoll
simply because that keeps the worker a bit neater. It’s a personal preference it doesn’t have to be written that way.
Note that the roll described here use d10’s. Sorcerer can use any die size as long as you are consistent, so you could change that here, but d10s are assumed in the roll template.
A standard roll in roll20 sums up the dice roll but we want to display each die rolled, in order from highest to lowest. There is a way to do that with the standard dice mechanic but it’s not pretty (see image above), so we’ll do it another way. We grab the dice
property, and create a sorted array. The .sort()
function takes a simple compare
function – this is how we sort from high to low.
Then in finishRoll
, we assign a couple of computed::
properties. For skill
, we store the skill name so we can display it in the roll template as shown below, and the sorted die are stored in the dice_roll
computed property.
Note that if you ever roll less than one die, roll one die and the opponent gains one extra. That last part has to be handled manually.
Building the Rolltemplate
The worker above mentions the sorcerer
rolltemplate, so we now need to build that. The HTML for the roll template is not complex, but notice where the computed::
results are.
<rolltemplate class="sheet-rolltemplate-sorcerer">
<div class="title">{{title}}</div>
<div class="properties">
<!-- need to put row headings -->
<div class="score">{{skill}}: {{score}}
{{#^rollTotal() modifier 0}}
{{#^rollLess() modifier 0}}+ {{/^rollLess() modifier 0}}
{{modifier}}
{{/^rollTotal() modifier 0}}
</div>
<div class="dice">{{computed::dice_roll}}</div>
</div>
</rolltemplate>
Code language: HTML, XML (xml)
There’s a big chunk on the modifier roll to find out when to display a + symbol.
Then we need to build the CSS. I’ve kept this fairly simple. Notice that rolltemplates still use legacy code, so we need to use the .sheet-
code before any class names.
.sheet-rolltemplate-sorcerer {
pointer-events: none;
margin-left: -37px;
background-color: black;
border: solid 5px white;
padding: 5px;
box-sizing: border-box;
border-radius: 15px;
color: white;
font-size: 120%;
}
.sheet-rolltemplate-sorcerer .sheet-title {
border-bottom: 2pt solid white;
font-size: 120%;
font-weight: bold;
}
/* the following code just stops those yellow roll boxes from appearing */
.sheet-rolltemplate-sorcerer .inlinerollresult,
.sheet-rolltemplate-sorcerer .inlinerollresult.fullfail,
.sheet-rolltemplate-sorcerer .inlinerollresult.fullcrit,
.sheet-rolltemplate-sorcerer .inlinerollresult.importantroll {
background-color: transparent;
border: none;
}
/* dice rolls appear as d10s */
.sheet-rolltemplate-sorcerer .sheet-properties .sheet-dice {
text-align: left;
font-family: dicefontd10;
font-size: 40px;
line-height: 30px;
}
Code language: CSS (css)
With all that work, it would be nice to see what a roll looks like. This template could be made a lot prettier, but the exercise was to create the sheet worker, not the rolltemplate, so we call it quits here.
Opposed Rolls, Rolls With Memory, and the Turn Tracker
There are easier ways to generate this dice roll, but we are setting up important steps for later.