CRP: Damage Rolls in Champions

There are several types of damage or effect rolls in Champions (or the Hero System), but the one we are concerned with here is Normal damage. Each roll generates two types of damage – STUN and BODY.

Roll a number of d6 and total them up to find the STUN. But look at each die. If it rolls a 6, it causes 2 BODY. A roll of 1 causes 0 BODY. And any other roll is 1 BODY. This means that each die causes 1 BODY and 3.5 STUN on average, but each varies. the fact you need to calculate two things from each roll makes this impossible to calculate with the standard Roll20 dice mechanics. But we can calculate this with Custom Roll Parsing.

This example uses startRoll to make a roll, calculates BODY and saves it to a computed property, and sends that to the roll template with finishRoll.

We’ll use a query to ask how many dice to roll. Later in the post, we’ll get the roll from an attribute on the character sheet, and include a query to switch between different types of roll.

The Basic System

The sheet contains a button (act_normal) which initiates a roll, and then the user is asked how many dice to roll.

<rolltemplate class="sheet-rolltemplate-custom">
    <div class="heading">{{name}}</div>
    <div class="results">
        <div class="roll">{{roll}}</div>
        <div class="result">{{result}}</div>
    </div>
</rolltemplate>Code language: HTML, XML (xml)
    on('clicked:normal', () => {
        startRoll(`&{template:champions} {{name=Champions Normal Damage Roll}} {{STUN=[[?{How many Dice|1}d6]]}}`, roll => {
            const dice = roll.results.STUN.dice;
            const calculate_body = die => die === 1 ? 0 : (die === 6 ? 2 : 1);
            const body = dice.reduce((sum, die) => sum + calculate_body(die),0);
            finishRoll(roll.rollId, {
                STUN: body
            });
        })
    });Code language: JavaScript (javascript)

Here we have a simple rolltemplate to show just the stun and body. Then we have the sheet worker which contains a roll that asks for howmany dice to roll.

It contains a function to calculate the body off each die (calculate_body), then uses the reduce function to loop through the dice and add them together. Finally, it saves the body total asthe computed value of STUN. The rolltemplate grabs that and displays it correctly.

That reduce function is tricky, but does the same work as this forEach loop:

let body = 0;
dice.forEach(die => body += calculate_body(die));Code language: JavaScript (javascript)

And there have it, a simple function to roll normal damage. But what if the damage rolls are stored in a repeating section?

From a Repeating Section

Lets say you have a section that a player uses to record all their special powers.

Each power has an Endurance cost, which reduces the characters Endurance attribute when used.

There are three types of damage in Champions:

  1. Normal Damage: roll a number of dice for STUN, get on average 1 BODY per die.
  2. Killing damage: roll a much smaller number of dice for BODY, and multiply by a die roll (or location multiplier) to get STUN
  3. NND: do stun only, as Normal damage. Do no Body damage. Certain other attacks, like Aids and Drains, can use the same method.
  4. None: some powers do no damage, but still cost endurance to use.

We” create a CRP worker and roll template that handles all of these.

A Typical Roll

A typical roll will look like this.

&{template:champions} {{name=Stabbing Sword}} {{body=[[0]]}} {{stun=[[0]]}} {{bodyx=1d6+2}} {{stunx=1d3}} {{type=[[1]]}} {{end=[[2]]}}

Body and stun are empty keys. The actual body and stun rolls will be saved as computed values.

stunx and bodyx are the expressions for those, so if you have a 7d6 roll for stun, stunx=7d6. This allows us to print out the actual damage roll.It would be nice to store them in the stun and body values (without the x), but those have to be numeric for us o be able to use computed values.

Type is the attack type – normal damage, killing, etc. We’ll be able to turn it from a number yo a label in the roll template.

Finally, characters have a total endurance and each power has an END cost. We’ll store the cost in the end key, and the endurance total as a computed value (or vice-versa).

Now we know what the roll is, we can sort out the coding.

The Repeating Section

Here’s the HTML for the section.

<span>Endurance: </span><input type="number" name="attr_Endurance" value="30">
<div class="champions-headings">
    <h4>Name</h4>
    <h4>Type</h4>
    <h4>Body</h4>
    <h4>Stun</h4>
    <h4>KB</h4>
    <h4>Roll</h4>
    <h4>END</h4>
</div>Code language: HTML, XML (xml)

That looks like this (with some very basic styling to stop it going too wide):

Notice there’s an unused KB column. Champions attacks have a knockback factor and we can implement that later- but it’s ignored for now.

<div style="color: #cccccc;background-color: #1f1f1f;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #d7ba7d;">.charsheet</span><span style="color: #cccccc;"> </span><span style="color: #d7ba7d;">.repitem</span><span style="color: #cccccc;"> </span><span style="color: #d7ba7d;">input</span><span style="color: #cccccc;"> {    </span><span style="color: #9cdcfe;">width</span><span style="color: #cccccc;">: </span><span style="color: #b5cea8;">50px</span><span style="color: #cccccc;">;}</span><span style="color: #d7ba7d;">.charsheet</span><span style="color: #cccccc;"> </span><span style="color: #d7ba7d;">.repitem</span><span style="color: #cccccc;"> </span><span style="color: #d7ba7d;">select</span><span style="color: #cccccc;">,</span><span style="color: #d7ba7d;">.charsheet</span><span style="color: #cccccc;"> </span><span style="color: #d7ba7d;">.repitem</span><span style="color: #cccccc;"> </span><span style="color: #d7ba7d;">input:first-of-type</span><span style="color: #cccccc;"> {    </span><span style="color: #9cdcfe;">width</span><span style="color: #cccccc;">: </span><span style="color: #b5cea8;">100px</span><span style="color: #cccccc;">;}</span><span style="color: #d7ba7d;">div.champions-headings</span><span style="color: #cccccc;">,</span><span style="color: #d7ba7d;">.repitem</span><span style="color: #cccccc;">[</span><span style="color: #9cdcfe;">data-groupname</span><span style="color: #d4d4d4;">=</span><span style="color: #ce9178;">"repeating_champions"</span><span style="color: #cccccc;">] {    </span><span style="color: #9cdcfe;">display</span><span style="color: #cccccc;">: </span><span style="color: #ce9178;">grid</span><span style="color: #cccccc;">;    </span><span style="color: #9cdcfe;">grid-template-columns</span><span style="color: #cccccc;">: </span><span style="color: #dcdcaa;">repeat</span><span style="color: #cccccc;">(</span><span style="color: #b5cea8;">2</span><span style="color: #cccccc;">,</span><span style="color: #b5cea8;">100px</span><span style="color: #cccccc;">) </span><span style="color: #dcdcaa;">repeat</span><span style="color: #cccccc;">(</span><span style="color: #b5cea8;">2</span><span style="color: #cccccc;">,</span><span style="color: #b5cea8;">50px</span><span style="color: #cccccc;">) </span><span style="color: #b5cea8;">45px</span><span style="color: #cccccc;"> </span><span style="color: #b5cea8;">30px</span><span style="color: #cccccc;"> </span><span style="color: #b5cea8;">45px</span><span style="color: #cccccc;">;    </span><span style="color: #9cdcfe;">column-gap</span><span style="color: #cccccc;">: </span><span style="color: #b5cea8;">5px</span><span style="color: #cccccc;">;}</span></div></div>Code language: CSS (css)

The Sheet Worker

The sheet worker is very complex, so lets break it down into steps.

    const section_name = (section, id, row) => `repeating_${section}_${id}_${row}`;
    const calculate_body = die => die === 1 ? 0 : (die === 6 ? 2 : 1);
    on('clicked:repeating_champions:roll', event_info => {
        getSectionIDs('repeating_champions', id_array => {
            const fields = id_array.reduce((all, id) => [...all, 
                section_name('champions', id, 'label'), 
                section_name('champions', id, 'attack_type'), 
                section_name('champions', id, 'body'), 
                section_name('champions', id, 'stun'), 
                section_name('champions', id, 'END'), 
            ], []);Code language: JavaScript (javascript)

This starts with two functions. First, the ever popular section_name function, which is incredibly useful whenever working with repeating sections. Then a calculate_body function – we’ll use that later, but it returns the body for normal damage attacks.

There are quite a few attributes of interest in this section. We get them all here.

Gathering Variables

getAttrs([...fields, 'endurance'], values => {
   const trigger = event_info.triggerName;
   const endurance = +values.endurance || 0;
   const id = trigger.split('_')[2];
   const label = values[section_name('champions', id, 'label')];
   const attack = +values[section_name('champions', id, 'attack_type')] || 0;
   const body = values[section_name('champions', id, 'body')];
   const stun = values[section_name('champions', id, 'stun')];
   const END = values[section_name('champions', id, 'END')];

   const roll_string = `&{template:champions} {{name=${label}}} {{body=[[${body}]]}} {{stun=[[${stun}]]}} {{bodyx=${body}}} {{stunx=${stun}}} {{type=[[${attack}]]}} {{end=[[${END}]]}}`; Code language: JavaScript (javascript)

I like to create a variable for each property I use. That makes things easier to debug when using console statements to figure out what a value is, and if it’s being calculated correctly. All the values I’ll need later are collected here, and we create the roll_string for the startRoll. This was described above.

The Actual Roll

setAttrs({
   endurance: endurance - END
});

startRoll(roll_string, roll => {
   const body_roll = roll.results.body.result;
   const stun_roll = roll.results.stun.result;
   const stun_dice = roll.results.stun.dice;
   const normal_body = stun_dice.reduce((sum, die) => sum + calculate_body(die),0);

   finishRoll(roll.rollId, {
      body: attack == 1 ? normal_body: attack === 2 ? body_roll : 0,
      stun: attack === 2 ? Math.floor(body_roll * stun_roll): (attack === 1 || attack === 3) ? stun_roll: 0,
      end: endurance
   });
});Code language: JavaScript (javascript)

For convenience, I place a setAttrs at the start. This saves the change to the endurance attribute.

Then, once again, I save things into variables for repeated use and easy debugging. Grabbing values out of the roll uses ROLL_NAME.results.KEY.SOMETHING, with something typically being result, dice, or expression.

More complex rolls have a different arrangement, but thats works here.

In finishRoll, we store the computed values using ternary operators. These are if statements on a single line. That could have been written different, like this:

   if(attack === 1) {
      finishRoll(roll.rollId, {
         body: normal_body,
         stun: stun_roll,
         end: endurance
      });
   } else if attack === 2) {
      finishRoll(roll.rollId, {
         body: body_roll,
         stun: Math.floor(body_roll * stun_roll),
         end: endurance
      });
   } else if attack === 3) {
      finishRoll(roll.rollId, {
         body: 0,
         stun: stun_roll,
         end: endurance
      });
   } else {
      finishRoll(roll.rollId, {
         body: 0,
         stun: 0,
         end: endurance
      });
   }
});Code language: JavaScript (javascript)

That might look cleaner, but they do the same thing, and I like ternary operators and will use them whenever I can 🙂

Don’t forget to add the correct number of }); to end the code.

We can’t really test this yet (except via the console.log method), so it’s time to build a roll template.

Roll Template

We want to distinguish between normal, killing, nnd, and other powers, which is why we pass the attack type as a numeric value. We can use logic helpers with it now, creating a lot of repetition!

<rolltemplate class="sheet-rolltemplate-champions">
    {{#rollTotal() type 1}}
        <div class="heading">{{name}} (Normal)</div>
        <div class="results">
            <div class="key">{{stunx}}</div>
            <div class="value">{{computed::stun}}s ({{computed::body}}b)</div>
        </div>
    {{/rollTotal() type 1}}
    {{#rollTotal() type 2}}
        <div class="heading">{{name}} (Killing)</div>
        <div class="results">
            <div class="key">{{bodyx}}</div>
            <div class="value">{{computed::body}}k ({{computed::stun}}s)</div>
        </div>
    {{/rollTotal() type 2}}
    {{#rollTotal() type 3}}
        <div class="heading">{{name}} (NND)</div>
        <div class="results">
            <div class="key">{{stunx}}</div>
            <div class="value">{{computed::stun}}s</div>
        </div>
    {{/rollTotal() type 3}}
    {{#rollTotal() type 0}}
        <div class="heading">{{name}} (Power)</div>
    {{/rollTotal() type 0}}
    <div class="results">
        <div class="key">ENDURANCE ({{computed::end}})</div>
        <div class="value">-{{end}}</div>
    </div>
</rolltemplate>Code language: JavaScript (javascript)

Every rolltemplate needs its CSS.

.sheet-rolltemplate-champions {
    background: white;
    border-radius: 5%;
}
.sheet-rolltemplate-champions .sheet-heading{
    background: black;
    color: white;
    text-align: center;
}
.sheet-rolltemplate-champions .sheet-results{
    display: grid;
    grid-template-columns: 49% 49%;
    column-gap: 2%;
    line-height: 1.8em;
}
.sheet-rolltemplate-champions .sheet-results div:first-child {
    text-align: right;
}Code language: CSS (css)

Here’s what that typically looks like. It’s not pretty – it’s a quick and dirty template. A lot could be done with it, but it gets the job done and shows this example.

Finishing Comments

There we are, a complete CRP example for callculating all three types of damage roll in Champions. It’s pretty complex! But there’s still more that could be done- adding a roll for knockback, handling armour in the same attack roll, and so on.

One thing this doesn’t account for is running the macros with the $0 syntax which tends to be used fr buttons in chat (like %{repeating_champions_$0_roll}). That topic will be handled in a future post. For now, these buttons will have to be launched from a character sheet.

Series Navigation<< Custom Roll Parsing: A Detailed ExampleCRP: Fudge and Fate >>

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.