Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

How to go about creating a custom sheet for multiple degrees of success?

February 22 (2 years ago)

I'm making a system that uses d100s measured against a character's skills to determine level of success (where 1 is the best you can roll and 100 is the worst):

  • Roll result is 1/5 of skill or lower = Critical Success
  • Roll result is 1/2 of skill or lower = Success
  • Roll result is lower than skill = Partial Success
  • Roll result is 50 + (Skill/2) or lower = Complication
  • Roll result is lower than 95 = Failure
  • Roll result is 95 or higher = Critical Failure

The intent is that the highest degree of success to be satisfied is used as the outcome. So, for example, if you had a Bowcraft skill of 50:

  • Rolling a 10 or lower = Critical Success
  • Rolling a 25 or lower = Success
  • Rolling a 50 or lower = Partial Success
  • Rolling a 75 or lower = Complication
  • Rolling a 76 - 95 = Failure
  • Rolling a 96 - 100 = Critical Failure

If this were a non-digital game, each skill would need a matrix that you'd need to look up every time to see what degree of success you got. With a virtual table top, however, I hope to make all of that happen in the background when a player clicks to roll on their sheet so it can immediately calculate and tell you what the degree of success is. 

My question is, if I were to create my own custom character sheet, how would I go about implementing the "if the roll is not this, then check if it is this" framework? I've been looking at the documentation and between Macros, Roll Templates, Custom Sheets, Sheetworkers, HTML, CSS, and Javascript, I'm not sure how to accomplish this the most efficiently. Where can I put the logic of "if the die roll satisfies this parameter, then this is the degree of success that will be posted in chat." Ideally I want to make it pretty too, but that can wait.

Thanks for the help.

February 22 (2 years ago)
GiGs
Pro
Sheet Author
API Scripter

If you were doing this in a custom character sheet, there are two main ways do to do it:


  1. Rolltemplate with Logic helpers (which will need HTML and CSS)
  2. Custom Roll Parsing (which will need HTML, CSS, Rolltemplate, and Javascript - and a fairly complicated interpretation of JS).

It should be obvious which I recommend. CRP is more powerful, but a lot trickier to set up.

You can nest logic helpers inside each other (and often need to).

To make this work, you need to suuply each category in the roll, like

{{roll=[[1d100]]}} {{skill=[[@{skill}]]}} {{critical=[[round(@{skill}/5)]]}} {{success=[[round(@{skill}/2)]]}} etc

You can then use the rolltemplate logic helpers to compare roll vs skill, critical, and so on.


I have questions about this bit:

  • Roll result is lower than skill = Partial Success
  • Roll result is 50 + (Skill/2) or lower = Complication

If skill is 100, those are equal. Above 100 and below 100 will give a different order. Does that every happen? If so you might need to go the CRP route to an excessively complicated rolltemplate.

February 22 (2 years ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Heh, I personally think the CRP route is easier, at least in the long term. Reading through a complicated roll template like that can be a nightmare.

I'd probably do it something like this (untested):
HTML

<input type="number" name="attr_skill">
<button type="action" name="act_skill-roll">Skill</button>
<rolltemplate class="sheet-rolltemplate-example">
  {{d100}}={{computed::result}}
</rolltemplate>

Sheetworker

// array list of our various skills
const skills = ['skill'];

skills.forEach(skill => {
  // listen for our button click
  on(`clicked:${skill}-roll`,() => {
    // get the value of our skill
    // use the async flag so we can use async/await with startRoll
    getAttrs([skill],async (attributes) => {
      // Send the roll, and wait for the result from the roll20 roller
      const roll = await startRoll(`&{template:example} {{d100=[[1d100]]}} {{result=[[0[computed value]]]}}`);
      // extract the result of our d100 so that we can work with it more easily
      const rollResult = roll.results.d100.result;
      // Create an empty object to hold the computations we will need to make.
      const computedObj = {};
      // Do the logic
      switch(true){
        case (rollResult <= (attributes[skill] / 5)):
          computedObj.result = 'Critical Success';
          break;
        case (rollResult <= (attributes[skill] / 2)):
          computedObj.result = 'Success';
          break;
        case (rollResult < attributes[skill]):
          computedObj.result = 'Partial Success';
          break;
        case (rollResult <= (50 + Math.floor(attributes[skill] / 2))):
          computedObj.result = 'Complication';
          break;
        case (rollResult < 95):
          computedObj.result = 'Failure';
          break;
        default:
          computedObj.result = 'Critical Failure';
          break;
      }
      // Send the manipulated rolls back to roll20 and release the roll for sending to chat.
      finishRoll(roll.rollId,computedObj);
    })
  });
});
February 23 (2 years ago)
GiGs
Pro
Sheet Author
API Scripter

That is easier to read than I was imagining the rolltemplate, but creating it in the first place takes a lot more internalised knowledge. But it's written now, so OP should definitely use it.