CRP: Fudge and Fate

In this post, I’ll describe how to make rolls for the Fudge RPG, which will easily be adapted to the Fate Rpg.

The thing that makes Fudge rolls special is the adjective ladder. Your skills and abilities are rated on this adjective ladder, so you might have Fighting Fair, and Charm Superb instead of say, Fighting +3 and Charm 12. When you make a roll, your output is one of these ranks, so, your success might be Terrible, Good, Great, or whatever.

Roll20 does support Fudge rolls (using for example /roll 4df). Fudge dice are special d6s, with two faces showing -, two showing +, and two showing 0. So a typical 4df roll gives a result ranging from +4 to -4, centred on +0. You take your starting rank, and move a number of ranks up or down based on your roll.

There are Fudge and Fate character sheets using Fudge dice rolls. But these give numeric outputs, like +3 and -1. It’s your job to translate these into ranks. In this post we’ll discover how to generate adjective outputs directly, using Custom Roll Parsing.

The Skills List

We’ll built a list of fixed skills, each with a button and rank, like this:

<div class="fudge">
    <h4>Skill</h4>
    <h4>Rank</h4>
    <button type="action" name="act_athletics">Athletics</button>
    <select name="attr_athletics">
        <option value="-3" selected>Terrible</option>
        <option value="-2" >Poor</option>
        <option value="-1" >Mediocre</option>
        <option value="0" >Fair</option>
        <option value="1" >Good</option>
        <option value="2" >Great</option>
        <option value="3" >Superb</option>
    </select>
</div>Code language: HTML, XML (xml)
.fudge {
    display:grid;
    grid-template-columns: 90px 100px;
}
.fudge button  {
    width: 80px;
    height: 20px;
}
.fudge select {
    width: 100px;
    height: 26px;
    margin-bottom: 1px;
}Code language: CSS (css)

It’s a simple layout. The key factor is each skill has a button and an attribute with the same name.

The skill values are selected from a dropdown, and not entered manually. This means typoes can’t happen, which is important to avoid when using sheet workers that depend on the correct spelling.

The CRP Sheet Worker

The sheet worker is a lot simpler than the previous one for Champions.

const ladder = ['terrible', 'poor', 'mediocre', 'fair', 'good', 'great', 'superb', 'legendary'];
const skills = ['athletics', 'charm', 'melee'];Code language: JavaScript (javascript)

We start with two arrays. We need a list of all the ranks in in the game. If you are using a different ladder you’d need to change this (as well s the list of options for the select above).

We also need a list of every skill in the game, using the same name assigned to the act_ and attr_ in the htmml above.

const capitalize = word => word[0].toUpperCase() + word.slice(1);

const skill_rank = (rank) => rank > 4 ? ("Legendary +" + Number(rank -4)) : (rank < -3) ? ("Terrible " + Number(rank +3)) : capitalize(ladder[rank + 3]);

const clicked = buttons => buttons.map(button => `clicked:${button}`).join(' ');

on(clicked(skills), event_info => {Code language: JavaScript (javascript)

Then we have some functions. capitalize simply takes a lower case word and capitalizes the first word, changing, say, athletics to Athletics.

skill_rank takes a number and gives us the adjective that refers to, so 1 becomes Good, -3 Terrible, and so on. This takes into account that 0 and Fair are both the midpoints.

Clicked takes a list of names and ceates a list of clicked: events. So when you have a listr of skill names, you can create a set of clicked: events for the first line of the sheet worker.

on(clicked(skills), event_info => {
    const skill = event_info.triggerName.replace('clicked:', '');
    getAttrs([skill], values => {
       const label = capitalize(skill);
       const rank = +values[skill] || 0;
       const roll_string = `&{template:fudge} {{name=${label}}} {{dice=[[4df]]}} {{rank=[[${rank}]]}} {{result=[[0]]}}`;Code language: JavaScript (javascript)

In the sheet worker, we first need to identify which skill was clicked. That’s what the const skill does.

rank gets the value of the skill, and it’s a number to make the roll easier. The rest here should need little explanation.

       startRoll(roll_string, roll => {
          const start = skill_rank(rank);
          const end = skill_rank(roll.results.roll.result + rank);
          const the_dice = roll.results.roll.dice;
          finishRoll(roll.rollId, {
             rank: start,
             roll: the_dice,
             result: end
          });
       });
    });
 });Code language: JavaScript (javascript)

In the roll, start and end represent the ladder rating (like good, or fair), and the_dice is an array of the four fudge dice rolled, so might be [1, 0, -1, 1].

In finishRoll we assign these to computed keys. In the roll_string, the last key is an empoty key named result. That only exists because we have three computed values, so we needed an extra key where we could save a value.

The Roll Template

Here is a simple roll template for that roll.

<rolltemplate class="sheet-rolltemplate-fudge">
    <div class="heading">{{name}} ({{computed::rank}})</div>
    <div class="results">
        <div class="key">Roll</div>
        <div class="value">
            <span class="dice"> 
                {{computed::roll}}
            </span> = {{roll}}
        </div>
        <div class="key">Result</div>
        <div class="value">{{computed::result}}</div>
    </div>
</rolltemplate>
Code language: HTML, XML (xml)

The result looks like this:

.sheet-rolltemplate-fudge {
    background: white;
    border-radius: 5%;
}
.sheet-rolltemplate-fudge .sheet-heading {
    background: black;
    color: white;
    text-align: center;
}
.sheet-rolltemplate-fudge .sheet-results {
    display: grid;
    grid-template-columns: 49% 49%;
    column-gap: 2%;
    line-height: 1.8em;
}
.sheet-rolltemplate-fudge .sheet-results .sheet-key {
    text-align: right;
}
.sheet-rolltemplate-fudge .sheet-dice {
    letter-spacing: 2px;
}
.sheet-rolltemplate-fudge .inlinerollresult {
    background-color: transparent;
    border: 0;
    padding: 0;
    cursor:auto;
    font-size: 1.0em;
}Code language: CSS (css)

There’s a lot you could do to make this prettier. Each individual die could be sent to the template, and put in CSS that made it look like a die, for instance. But this post is just about showing you how to do the roll.

Final Comments

So there you have it. A roll which takes an adjective, adds a numeric value to it, and spits out a different adjective. It also shows each die result (you could tweak the rolltemplate to make those pretty looking dice). Fate has a different adjective list to Fudge, but it’s easy enough to plug them in as alternatives.

Here’s the sheet worker again, completed.

const ladder = ['terrible', 'poor', 'mediocre', 'fair', 'good', 'great', 'superb', 'legendary'];
const skills = ['athletics', 'charm', 'melee'];
const capitalize = word => word[0].toUpperCase() + word.slice(1);
const skill_rank = (rank) => rank > 4 ? ("Legendary +" + Number(rank -4)) : (rank < -3) ? ("Terrible " + Number(rank +3)) : capitalize(ladder[rank + 3]);
const clicked = buttons => buttons.map(button => `clicked:${button}`).join(' ');

on(clicked(skills), event_info => {
   const skill = event_info.triggerName.replace('clicked:', '');
   getAttrs([skill], values => {
      const label = capitalize(skill);
      const rank = +values[skill] || 0;
      const roll_string = `&{template:fudge} {{name=${label}}} {{dice=[[4df]]}} {{rank=[[${rank}]]}} {{result=[[0]]}}`;
      startRoll(roll_string, roll => {
         const start = skill_rank(rank);
         const end = skill_rank(roll.results.roll.result + rank);
         const the_dice = roll.results.roll.dice;
         finishRoll(roll.rollId, {
            rank: start,
            roll: the_dice,
            result: end
         });
      });
   });
});Code language: JavaScript (javascript)
Series Navigation<< CRP: Damage Rolls in ChampionsCRP: Criticals on Multiple Dice (HERO and GURPS) >>

Leave a Reply

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