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

Rolling higher-than and lower-than simultaneously

February 09 (3 years ago)
Aero
Pro

I understand that a roll formula can count the number of rolled values that equal or exceed a set threshold. E.g. 4d6>6 will count the number of 6s rolled on four six-sided dice.

And of course it can be set as equal to or lower. E.g. 4d6<1 will count the number of 1s rolled on four six-sided dice.

But is it possible to do both at the same time?

I.e. Can I instruct the roll button to roll a number of dice and count both the number of 1s and the number of 6s as two separate outputs?

February 09 (3 years ago)

Edited February 10 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

No, unfortunately this isn't possible, at least not with roll20's standard dice macros. The question has been raised before. You cant perform two different operations on the same dice roll.

As a Pro user, there are API scripts you can use to achieve this (I think you could do it with ScriptCards for instance).

February 09 (3 years ago)
Finderski
Sheet Author
Compendium Curator

If this is for a custom sheet you're developing, you might look into the Custom Roll Parsing as I believe you could accomplish this with that...

February 09 (3 years ago)
Aero
Pro

Hmm, maybe time I started looking into this API craze...

Cheers GiGs.

February 09 (3 years ago)
Finderski
Sheet Author
Compendium Curator

Just a note, the Custom Roll Parsing isn't API and is available to character sheets... Again, assuming this is for a custom character sheet, or a character sheet you're developing...

February 09 (3 years ago)

Edited February 10 (3 years ago)
David M.
Pro
API Scripter

This would be pretty straightforward using the Scriptcards api. Query for number of d6, then loop through the num dice assigning roll variables to each and counting 1's and 6's. Output when done.

EDIT 2 - My bad, didn't notice this was on the character sheet forum haha. Disregard.

EDIT - Ok, here's an example :)

!script {{
  --#title|1's and 6's example
  --=numDice|?{How many d6's?|4}
  --#leftsub|Rolling [$numDice]d6

  --=numOnes|0
  --=numSixes|0

  --=i|0
  --:Loop|
      --=i|[$i]+1
      --=roll[$i.Raw]|1d6
      --&rollStr|+[$roll[$i.Raw]]
      --?[$roll[$i.Raw]] -eq 1|=numOnes;[$numOnes]+1
      --?[$roll[$i.Raw]] -eq 6|=numSixes;[$numSixes]+1
      --?[$i] -lt [$numDice]|Loop

  --+rolls|[&rollStr]
  --+Num Sixes|[$numSixes]
  --+Num Ones|[$numOnes]
}}


February 10 (3 years ago)
Aero
Pro


Finderski said:

Just a note, the Custom Roll Parsing isn't API and is available to character sheets... Again, assuming this is for a custom character sheet, or a character sheet you're developing...

Now that looks just the thing, thanks! Would you (or anyone) perhaps be able to give me another nudge in the learning process?

I've got the example working in my sheet and see that it returns both the total of the dice and what that value is mod4. How would I go about converting it to look at the individual dice values? Like counting 1s and 6s, or anything under or over a particular number.

<button type="action" name="act_test">Click me</button>
<rolltemplate class="sheet-rolltemplate-test">
        <h1>{{name}}</h1>
        <div class="sheet-results">
            <h4>Result = {{roll1}}</h4>
            <h4>Custom Result = {{computed::roll1}}</h4>
        </div>
</rolltemplate>
<script type="text/worker">
    on('clicked:test', (info) => {
        startRoll("&{template:test} {{name=Test}} {{roll1=[[@{fight}d6]]}}", (results) => {
            const total = results.results.roll1.result
            const computed = total % 4;
            finishRoll(
                results.rollId,
                {
                    roll1: computed,
                }
            );
        });
    });
</script>
February 10 (3 years ago)

Edited February 10 (3 years ago)
Finderski
Sheet Author
Compendium Curator

In your example above, instead of looking at result, you can look at the array of dice. So something like this:

const total = results.results.roll1.result
const diceValues = results.results.roll1.dice; //This will be an array of the values rolled on all the dice
let totalGreater = 0;
for(let i = 0; i < diceValues.length; i++) {
    totalGreater += diceValues[i] > 5 ? 1 : 0
}

GiGs can probably get you a better version of this, but it should get you started in the right direction.

Also, this is untested, so there are likely errors... :-/

NOTE: I left your const total in there, just in case you needed it...and to give you a reference for where I was talking about...

February 10 (3 years ago)

Edited August 25 (1 year ago)
GiGs
Pro
Sheet Author
API Scripter

haha I didnt notice this was on the custom sheet forum either, hence my advice didnt focus on custom roll parsing. If you're creating your own sheet, thats the way to go.

CSP has the problem that its triggered by action buttons, so if you want to use it in multiple places in your sheet you have to create a lot of sheet worker code, or follow Scott's superior suggestion to have the buttons encoded in a data object, so you can use one worker for all such buttons on a sheet.

The first thing is you need to have variables stored for your rolltemplate, so your button would initially look like

<script type="text/worker">
    on('clicked:test', (info) => {
        startRoll("&{template:test} {{name=Test}} {{roll=[[@{fight}d6]]}} {{high=[[0]]}} {{low=[[0]]}}", (diceroll) => {


        });
    });
</script>

You need to start by creating 0 values, high and low, so you can add computed values to them (the number of 6s and number of 1s).

Also I find the standard roll20 choices of variable names to be very confusing, so i changed results to diceroll here.

Then expanding with finderski's method, you would get this (personally I'd use filter here instead of a for loop, but a for loop works just fine):

<script type="text/worker">
    on('clicked:test', (info) => {
        startRoll("&{template:test} {{name=Test Fight}} {{roll=[[@{fight}d6]]}} {{high=[[0]]}} {{low=[[0]]}}", (diceroll) => {
            const total = diceroll.results.roll.result
            const dice = diceroll.results.roll.dice; //This will be an array of the values rolled on all the dice
            let total6s = 0;
            let total1s = 0;
            for(let i = 0; i < dice.length; i++) {
                total6s += dice[i] > 5 ? 1 : 0;
                total1s += dice[i] < 2 ? 1 : 0;
            }
            finishRoll(
                diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                {
                    roll: total,
                    high: total6s,
                    low: total1s
                }
            );
        });
    });
</script>

Then in your rolltemplate, you'd need to capture the high (number 6s) and low (number 1s), so you could show them.

Going back to Finderski's method, as an example:

<rolltemplate class="sheet-rolltemplate-test">
        <h1>{{name}}</h1>
        <div class="sheet-results">
            <h4>Result = {{roll}}</h4>
            <h4>Number 6s = {{computed::high}}</h4>
            <h4>Number 1s = {{computed::low}}</h4>
        </div>
</rolltemplate>

You use the computed:: operator to get the values created in the sheet worker.

I created a computed::roll, though it's not actually needed.

NEXT STEP

<snipped to avoid confusion - see next post>
February 10 (3 years ago)

Edited February 11 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter
Here's a working example of a complete solution. It's split into three sections - the basic html, the rolltemplate, and the sheet worker (CRP).

First you need to set up the attributes and the action buttons for them. In this example, for simplicity, I've given the action buttons exactly the same name as the attributes. It's not necessary to do that, but it makes your life a lot easie (and is necessary for this example)r:

<div class="stats">
    <button type="action" type="number" name="act_strength">Strength</button>
    <input name="attr_strength" type="number" value="0">

    <button type="action" name="act_dexterity">Dexterity</button>
    <input name="attr_dexterity" type="number" value="0">
   
    <button type="action" name="act_constitution">Constitution</button>
    <input name="attr_constitution" type="number" value="0">
   
    <button type="action" name="act_intelligence">Intelligence</button>
    <input name="attr_intelligence" type="number" value="0">
   
    <button type="action" name="act_wisdom">Wisdom</button>
    <input name="attr_wisdom" type="number" value="0">
   
    <button type="action" name="act_charisma">Charisma</button>
    <input name="attr_charisma" type="number" value="0">
</div>

I've just created sample code for six attributes and their action buttons, with no styling.


Then the roll template:


<rolltemplate class="sheet-rolltemplate-diceroll">
    <h1>{{name}}</h1>
    <div class="sheet-results">
        <h4>Result = {{roll}}</h4>
        <h4>Number 6s = {{computed::high}}</h4>
        <h4>Number 1s = {{computed::low}}</h4>
    </div>
</rolltemplate>


Again no styling, I don't know how you want it to be displayed.  You can see how it's using computed values for high and low.


Finally the most important pasrt, the sheet worker might need a bt of explanation. Remember its enclosed in a script block.

First you need to create an array of the attribute names. If you add extra action buttons with matching attributes, expand this list. Really, that's the only change you need to make, based on previous code. This array can get really big - that's fine. Don't worry about that.

const stats = [
    'strength',
    'dexterity',
    'intelligence',
    'wisdom',
    'constitution',
    'charisma'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
            startRoll(`&{template:diceroll} {{name=${button}}} {{roll=[[@{${button}}d6]]}} {{high=[[0]]}} {{low=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice; //This will be an array of the values rolled on all the dice

                const total6s = dice.filter(d => d === 6).length;
                const total1s  = dice.filter(d => d === 1).length;
                finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        high: total6s,
                        low: total1s
                    }
                );
            });
        });
});

Then the forEach loop creates a separate worker for each action button. You can make this more efficient by using a single worker for all buttons, but this approach is easier to code.

button in the above code is the name of the action button, which remember also matches exactly an attribute name. So, the forEach loop there is creating a separate sheet worker for each action button, with the proper button and attribute names.


After this you'd need to add your own styling to make the various elements look nice. And change the stats array to match whatever button names you are using.

February 11 (3 years ago)
Aero
Pro

Wow, this is great, thanks all!

GiGs – could you possibly walk me through the generalisation step? Not sure I get it...

So a sheet worker creates new objects, one for each relevant stat (fight, scan, etc.), and sets each at an arbitrary value (so presumably all 0 to start with). Then another sheet worker is set to trigger whenever a stat is changed and the effect of the second worker is to change the value of the object in the first sheet worker, thus keeping those objects at the same value as the value set on the character sheet? Am I on the right lines?

February 11 (3 years ago)

Edited February 11 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

I changed method between the first post and second. For general use just look at the second.

Since your code uses @{stat}d6 syntax, there's no need to monitor attribute values with sheet workers. A standard roll macro can get the value of stats with that syntax, no sheet worker needed.

So the only sheet worker needed (in this case a loop of sheet workers) is to do the rolls. (That's why I deleted some code from the first post - it would confuse things.)

The basic html sets default value of 0, but you could set that default value to anything. If this was dnd, I'd probably have value="10" not 0.

So in the script block, you first have an array of all attribute names (which might be stats, skills, whatever-  in roll20 everything with a value is called an attribute).

The forEach loop then iterates through that array, and for each item creates a CRP worker, with the word 'button' replaced with the current attribute name.


February 11 (3 years ago)
Aero
Pro

Amazing. I think I'm almost ready to leave you alone, just one more thing...

I tried adding a second roll into the script worker (straight 4d6) and duplicated the code to extract the same information (justed added 'z' to all the names).

I got this to work on your first method, but with this one it's pulling the roll total but not the counts of 1s and 6s. Can you spot my mistake(s)?

<div class="stats">
    <button type="action" type="number" name="act_strength">Strength</button>
    <input name="attr_strength" type="number" value="0">
    <button type="action" name="act_dexterity">Dexterity</button>
    <input name="attr_dexterity" type="number" value="0">
    
    <button type="action" name="act_constitution">Constitution</button>
    <input name="attr_constitution" type="number" value="0">
    
    <button type="action" name="act_intelligence">Intelligence</button>
    <input name="attr_intelligence" type="number" value="0">
    
    <button type="action" name="act_wisdom">Wisdom</button>
    <input name="attr_wisdom" type="number" value="0">
    
    <button type="action" name="act_charisma">Charisma</button>
    <input name="attr_charisma" type="number" value="0">
</div>

<rolltemplate class="sheet-rolltemplate-diceroll">
    <h1>{{name}}</h1>
    <div class="sheet-results">
        <h4>Result = {{roll}}</h4>
        <h4>Number 6s = {{computed::high}}</h4>
        <h4>Number 1s = {{computed::low}}</h4>
        <br />
        <h4>Result = {{rollz}}</h4>
        <h4>Number 6s = {{computed::highz}}</h4>
        <h4>Number 1s = {{computed::lowz}}</h4>
    </div>
</rolltemplate>

<script type="text/worker">
const stats = [
    'strength',
    'dexterity',
    'intelligence',
    'wisdom',
    'constitution',
    'charisma'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
            startRoll(`&{template:diceroll} {{name=${button}}} {{rollz=[[4d6]]}} {{roll=[[@{${button}}d6]]}} {{high=[[0]]}} {{low=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                const totalz = diceroll.results.rollz.result
                const dicez = diceroll.results.rollz.dice; //This will be an array of the values rolled on all the dice
                
                const total6s = dice.filter(d => d === 6).length;
                const total1s  = dice.filter(d => d === 1).length;
                const totalz6s = dicez.filter(d => d === 6).length;
                const totalz1s  = dicez.filter(d => d === 1).length;
                finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        high: total6s,
                        low: total1s,
                        rollz: totalz,
                        highz: totalz6s,
                        lowz: totalz1s,
                    }
                );
            });
        });
});
</script>
February 12 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

Is this 4 d6 a completely separate roll, or something that is done alongside every other roll?

If it's a separate roll, you'd be better off putting it as a hidden attribute, and giving it a name you used to call it. like

<button type="action" class="hide" name="act_four"></button>
<input name="attr_four" type="hidden" value="0">

That creates a hidden attribute and action button, then just change the array:

const stats = [
    'strength',
    'dexterity',
    'intelligence',
    'wisdom',
    'constitution',
    'charisma',
    'four'
];

and dont change the sheet worker at all - this would let you call a four dice button any time you want.


You can call this ability with %{CHARACTER-NAME|4}, replacing characetr_name which the character's actual name.

Or if clicking a token, use %{selected|four} or %{target|four}


If given the button class="hide" - thats just to show you'll have to use give it a class name and use CSS to hide it, like

button.hide {
    display:none;
}

or

button.sheet-hide {
    display:none;
}

(which of those is correct depends what CSS rule you are using for your sheet. Try both.)


You could add buttons for other rolls, like 3d6, 9d6, whatever.

Buttons Without Attributes

If you are creating a lot of standard rolls like that, it would be better to change the sheet worker a little bit to handle rolls without attributes attached, where you have one button which asks for the dice, and you roll that.


Add something like this to the html:
<button type="action" class="hide" name="act_ask"></button>


and change the sheet worker to account for this:

<script type="text/worker">
    const stats = [
        'strength',
        'dexterity',
        'intelligence',
        'wisdom',
        'constitution',
        'charisma',
        'four',
        'ask'
    ];
    stats.forEach(button => {
        on(`clicked:${button}`, () => {
                const roll_text = (button === 'ask') ?
                `&{template:diceroll} {{name=?{How many Dice?|4} Dice}} {{roll=[[?{How many Dice?}d6]]}} {{high=[[0]]}} {{low=[[0]]}}` :
                    `&{template:diceroll} {{name=${button}}} {{roll=[[@{${button}}d6]]}} {{high=[[0]]}} {{low=[[0]]}}`
                if(button === 'ask') {

                }
                startRoll(roll_text, (diceroll) => {
                    const total = diceroll.results.roll.result
                    const dice = diceroll.results.roll.dice; //This will be an array of the values rolled on all the dice

                    let total6s = dice.filter(d => d === 6).length;
                    let total1s  = dice.filter(d => d === 1).length;
                    finishRoll(
                        diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                        {
                            roll: total,
                            high: total6s,
                            low: total1s
                        }
                    );
                });
            });
    });
</script>


Then when you call %{selected|ask} or whatever, you'll get prompted for hor many dice to roll.

You might remove the hidden class, and put this button somewhere prominant, and give it a name, so it can be clicked manually too.

February 12 (3 years ago)

Edited February 12 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

If on the other hand, you want to add a 4d6 roll to every roll, the answer is easier. My original roll was this:

&{template:diceroll} {{name=${button}}} {{rollz=[[4d6]]}} {{roll=[[@{${button}}d6]]}} {{high=[[0]]}} {{low=[[0]]}}


See the bit it at the end,

{{high=[[0]]}} {{low=[[0]]}}


Those only exist so that variables are created by the roll to store your high and low value later. It's a hack that a roll20 member came up with, to allow you to pass more values to the rolltemplate than there are rolls.

So for your roll, you'd just need to change the end from

{{high=[[0]]}} {{low=[[0]]}}`


to

{{high=[[0]]}} {{low=[[0]]}} {{highz=[[0]]}} {{lowz=[[0]]}}`


Note: personally I'd change the z ending of those variables to 4, like high4, low4, roll4. It's more descriptive of what the variable actually is.

February 12 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

Note if you do want a second die roll, you could change the

{{rollz=[[4d6]]}}

to a query, asking for the number of dice, like:

{{rollz=[[?{How Many Dice?|4}]]}}

But you dont need to do that if its always 4.

February 12 (3 years ago)
Aero
Pro

See the bit it at the end,

Yes, that's it! That's the one thing I forgot to replicate. Thank yo so much, this has been really really helpful!

February 13 (3 years ago)
Aero
Pro

Ok, maybe a little more if you have the time and patience!

What would the code look like if I wanted to return the highest number rolled? Can't seem to get Math.max to do it.

Also, say I had a roll query that asked the player to select a text option from a list:

{State=?{State|A|B|C}}}

What I'd like to do is display variable text in the output according to both the roll and the option they picked. I can do the roll bit with things like -

{{#rollGreater() computed::six 0}}TEXT{{/rollGreater() computed::six 0}}

But how would I achieve this for text selected in a roll query?

For example, the above will display 'TEXT' if at least one 6 was rolled. But suppose I wanted to show something different if at least one 6 was rolled AND State B had been selected?

February 13 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

what does computed::six represent above?

Rolltemplate logic helpers only work with numbers, not text values, so you'd need to do that test inside the sheet worker.
February 13 (3 years ago)
Aero
Pro

Sorry, computed::six is what I'm using now for the number of sixes rolled.

So for the rollquery I need to convert the options into numerical values then? Something like three values called stateA, stateB and stateC with each one outputting either 0 or 1 – specifically, the option chosen will create the value 1 in the corresponding stateX whilst the unselected options will leave their corresponding values at 0.

Could you show me an example of what that would look like? Or a link to somewhere that explains this kind of thing? I get very confused between the different and incompatible structures!

February 13 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

Going back to an earlier comment, if you want to find the maximum number rolled, you have an array of the dice rolls in the worker:

const dice = diceroll.results.roll.dice; 

You can get the max value using the spread operator (...), this expands an array into individual numbers:

const highest_roll = Math.max(...dice);

Exactly how you use that and pass it to your rolltemplate depends on what you are doing with it.

February 13 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter


Rich K. said:

So for the rollquery I need to convert the options into numerical values then? Something like three values called stateA, stateB and stateC with each one outputting either 0 or 1 – specifically, the option chosen will create the value 1 in the corresponding stateX whilst the unselected options will leave their corresponding values at 0.

Could you show me an example of what that would look like? Or a link to somewhere that explains this kind of thing? I get very confused between the different and incompatible structures!

If you want to do it in the rolltemplate yes, and remember, to be recognises as numbers, they have to be inlinerolls (surrounded by [[ ]] brackets for the logic functions to recognise them).

If you're doing it in the rolltemplate, you can only do one logical comparison at a time, but can nest many levels down, so you'd need to do something like (not real code):

{{#rollGreater() computed::six 0}}
    {{#rollTotal() stateC 1}}
        show what you want to show when these are both true
    {{/rollTotal() stateC 1}}
{{/rollGreater() computed::six 0}}

 That said, if you are doing complicated calculations and comparisons, it's better to do them in the sheet worker, where you have access to javascript and can do things much more easily, then save a value of 0 or 1 to a computed roll value, and just have one logic check in the rolltemplate.


February 13 (3 years ago)
Aero
Pro
GiGs said:
Going back to an earlier comment, if you want to find the maximum number rolled, you have an array of the dice rolls in the worker:
const dice = diceroll.results.roll.dice;
You can get the max value using the spread operator (...), this expands an array into individual numbers:
const highest_roll = Math.max(...dice);
Exactly how you use that and pass it to your rolltemplate depends on what you are doing with it.

Doesn't seem to be working - I've tried with max and min and both are coming out as 0. Here's the code:

const stats = [
    'Fight'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
            startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d6s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                
                const high = Math.max(...dice);
                const low = Math.min(...dice);
                finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        highest: high,
                        lowest: low
                    }
                );
            });
        });
});
February 13 (3 years ago)
Aero
Pro


GiGs said:

{{#rollGreater() computed::six 0}}
    {{#rollTotal() stateC 1}}
        show what you want to show when these are both true
    {{/rollTotal() stateC 1}}
{{/rollGreater() computed::six 0}}

Oh yes, this is exactly what I want to do (not least because I've actually managed to get my head around rolltemplate logic statements!).

But how would I get the script worker to create a value for stateC?

The rollquery is like this:

{{State=?{State|A|B|C}}}

How do I turn the player's selection into a value that is 1 if they picked 'C' and 0 if they didn't?

February 13 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter


Rich K. said:

GiGs said:
Going back to an earlier comment, if you want to find the maximum number rolled, you have an array of the dice rolls in the worker:
const dice = diceroll.results.roll.dice;
You can get the max value using the spread operator (...), this expands an array into individual numbers:
const highest_roll = Math.max(...dice);
Exactly how you use that and pass it to your rolltemplate depends on what you are doing with it.

Doesn't seem to be working - I've tried with max and min and both are coming out as 0. Here's the code:

const stats = [
    'Fight'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
            startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d6s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                
                const high = Math.max(...dice);
                const low = Math.min(...dice);
                finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        highest: high,
                        lowest: low
                    }
                );
            });
        });
});


This looks correct. Can you post the rolltemplate code you're using?
You can also add a logging line, like so:

                const high = Math.max(...dice);
                const low = Math.min(...dice);
                console.log(`high: ${high}; low: ${low}`);

Then with the console open (typically F12 then check the console tab), you'll see the values being displayed.

If nothing is appearing then the issue is likely to be a sheet worker that comes before this one having a syntax error causing the sandbox to break.

But its maybe more likely there's something iffy in your rolltemplate.

February 13 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter


Rich K. said:


GiGs said:

{{#rollGreater() computed::six 0}}
    {{#rollTotal() stateC 1}}
        show what you want to show when these are both true
    {{/rollTotal() stateC 1}}
{{/rollGreater() computed::six 0}}

Oh yes, this is exactly what I want to do (not least because I've actually managed to get my head around rolltemplate logic statements!).

But how would I get the script worker to create a value for stateC?

The rollquery is like this:

{{State=?{State|A|B|C}}}

How do I turn the player's selection into a value that is 1 if they picked 'C' and 0 if they didn't?


It looks like you do need to convert it to a number, just like with a rolltemplate. So if you add something like

{{query=[[?{query|A,1|B,2|C,3}]]}}

to the roll string (and note the [[ ]] inline roll brackets), you can work with the number by grabbing it:

const query = diceroll.results.query.result;


Then you can perform javascript matching, like if you want to display something if Query is 2, and max roll is 6, you can do:

let query_test = 0;
if(high === 6 && query === 2) {
    query_test = 1;
}
finishRoll(
diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
{
roll: total,
highest: high,
lowest: low
query: query_test }
);

Note that in the if statement && means AND, so you are checking that both those tests are true.

Now you have a single value that is 0 or 1, and can test rollTotal, like

{{#rollTotal() computed::query 1}}


This way might seem more complicated now, but when you are building more and more complicated logical tests, it saves a lot of work and avoiding complicated nested rollTemplates helpers.


February 13 (3 years ago)
Aero
Pro

GiGs said:

But its maybe more likely there's something iffy in your rolltemplate.

Yep, forgot the computed:: bit AGAIN. Sorry!

{{facepalm}}

February 13 (3 years ago)
Aero
Pro


GiGs said:

Now you have a single value that is 0 or 1

Brilliant!! I thank your masterful expertise once more!

it saves a lot of work and avoiding complicated nested rollTemplates helpers
No kidding!

February 14 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter


Rich K. said:

GiGs said:

But its maybe more likely there's something iffy in your rolltemplate.

Yep, forgot the computed:: bit AGAIN. Sorry!

{{facepalm}}

Hehe, we've all made that kind of goof.

And to the other post, you're welcome :)


March 03 (3 years ago)
Aero
Pro

I've got a bit more adventurous with this and could do with a further pointer if you'd oblige me ...

I've incorporated a setAttrs element to automatically adjust stats according to the roll, like so:

<div class="stats">
<input name="attr_fight" type="number" value="0" />
<input name="attr_dodge" type="number" value="0" />
<br />
<input name="attr_fightstat" type="number" value="0" /> 
<input name="attr_dodgestat" type="number" value="0" />
<br />
<button type="action" type="number" name="act_fight">FIGHT</button> 
<button type="action" type="number" name="act_dodge">DODGE</button> 
</div>

<rolltemplate class="sheet-rolltemplate-diceroll">
{{computed::roll}}<br />
{{computed::highest}}<br />
{{computed::lowest}}
</rolltemplate>
<script type="text/worker">
const stats = [
    'fight',
    'dodge'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
    getAttrs(['fightstat', 'dodgestat'], v=> {
            startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d6s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                
                const high = Math.max(...dice);
                const low = Math.min(...dice);
                finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        highest: high,
                        lowest: low
                    }
                );
                setAttrs({
                    fightstat: high,
                    dodgestat: high
                });
            });
        });
    });
});
</script>

So this sets both fightstat and dodgestat  to whatever the highest die roll is, but what if I only want to change fightstat if I'm rolling fight and only change dodgestat if I'm rolling dodge? Of course I could just split it into two sheetworkers, but it would be more elegant and easier on the rolltemplate not to.

I tried this:

<script type="text/worker">
const stats = [
    'fight',
    'dodge'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
    getAttrs(['fightstat', 'dodgestat'], v=> {
            startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d6s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                
                const high = Math.max(...dice);
                const low = Math.min(...dice);
let fightyes = 0;
if(name === 'fight') {
    fightyes = 1;
}
let dodgeyes = 0;
if(name === 'dodge') {
    dodgeyes = 1;
}
                    finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        highest: high,
                        lowest: low
                    }
                );
                setAttrs({
                    fightstat: (fightyes * high) + ((1 - fightyes) * fightstat),
                    dodgestat: (dodgeyes * high) + ((1 - dodgeyes) * dodgestat)
                });
            });
        });
    });
});
</script> 

But that breaks the setAttrs and leaves fightstat and dodgestat unchanged by the roll.

Is it the if syntax that's wrong?

March 03 (3 years ago)

Edited March 03 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

How are you setting name in your function? You seem to be assuming it has a value, but havnt assigned it anyway that I see.

Also this calculation:

fightstat: (fightyes * high) + ((1 - fightyes) * fightstat)

that looks like the sort of thing you'd use in a roll20 macro, but in JS you have a full programming language so dont have to use clunky tricks.


At the start of your code you have this line:

stats.forEach(button => {

This says that the stats array is being iterated through, one be one, and for each entry, it's running the following code, and you can use button in place of the attribute name.

Try the following code:

const stats = [
    'fight',
    'dodge'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
        startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d6s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
            const total = diceroll.results.roll.result
            const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice

            const high = Math.max(...dice);
            const low = Math.min(...dice);

            finishRoll(
                diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                {
                    roll: total,
                    highest: high,
                    lowest: low
                }
            );
            setAttrs({
                [`${button}stat`]: high
            });
        });
    });
});

Since the code is being run only for fightstat or dodgstat at any given time, you can use button to refer to the one that is active.

By using`${button}stat` we build a copy of the stat name. This can also be written as

button + "stat"

- thats an older syntax for adding text to variable names, but the method I've used is generally more comfortable once you've used it a bit.

March 03 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

I just altered the previous code. I notice you arent using getAttrs, so it can be removed from the code.

You only need getAttrs if you are grabbing attribute values from the sheet, and then using those values in the code. you are just using th attribute name, so you don't need to do that.


Also I dont include the <script> </script> block, because thats assumed. It's not part of the function, it's just a container that all sheet workers should go inside.

March 03 (3 years ago)
Aero
Pro

Fantastic, cheers GiGs!

March 03 (3 years ago)
Aero
Pro

Ah, now how does it work when referencing the button in the formula?

Say I wanted the associated stat to go up by one with each roll. I tried this but it didn't work:

[`${button}stat`]: [`${button}stat`] + 1
March 03 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

If you're doing anything with the attribute value directly, you do need to use get Attrs, and first have to get the value from the sheet. For example here are the separate steps

// first get the value from the sheet
let score = +v[`${button}stat`] || 0;

// then modify it
score = score +1;

// then save it back to the sheet
setAttrs({
    [`${button}stat`]: score
});

You might combine them in different ways (if you have another setAttrs, you'd want to combine them.

You'd need to add the getAttrs line back into the code. Which remember only needs to grab the relevant attribute:

getAttrs([`${button}stat`], v=> {

// your code here, but dont forget to add a closure for this:

});


March 03 (3 years ago)
Aero
Pro

Not working – I must have gone wrong somewhere:

const stats = [
    'fight',
    'dodge'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
    getAttrs([`${button}stat`], v=> {
            startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d20s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                
                const high = Math.max(...dice);
                const low = Math.min(...dice);
                let score = +v[`${button}stat`] || 0;
                let score = score + 1;
                    finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        highest: high,
                        lowest: low
                    }
                );
                setAttrs({
                    [`${button}stat`]: score
                });
            });
        });
    });
});
March 03 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

what do do the html for your fightsta and dodgestat  look like?

March 03 (3 years ago)
Aero
Pro

Like so:

<div class="stats">
<input name="attr_fight" type="number" value="0" />
<input name="attr_dodge" type="number" value="0" />
<br />
<input name="attr_fightstat" type="number" value="0" /> 
<input name="attr_dodgestat" type="number" value="0" />
<br />
<button type="action" type="number" name="act_fight">FIGHT</button> 
<button type="action" type="number" name="act_dodge">DODGE</button> 
</div>

I have got it working by using

[`${button}stat`]: score + 1

instead of

let score = score + 1;

but it would be good to understand your way.

March 03 (3 years ago)

Edited March 03 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

The problem in my code is here

let score = +v[`${button}stat`] || 0;
let score = score + 1;

that should be

let score = +v[`${button}stat`] || 0;
score = score + 1;

The first line creates a variable called score. In the original code, the second line attents to create a new variable also named score (thats what let does), when i should just be modifying the original variable.

March 09 (3 years ago)

Edited March 09 (3 years ago)
Aero
Pro

I've got another one...

What would be the best way for the rolltemplate to know which button was clicked?

For example, if 'fight' was clicked for the roll then I'd like it to print something like 'Attacking' whereas if 'dodge' was clicked I'd like it to say 'Ducking'.

I thought about putting something like this in the roll template:

{{#rollTotal() name fight}}Attacking{{/rollTotal() name fight}}
{{#rollTotal() name dodge}}Ducking{{/rollTotal() name dodge}}

But it doesn't seem to work with text values.

I thought maybe some if statements to convert the choice of button into a number, but if that's possible then I've not been able to figure out the syntax.

Here's the sheetworker code again to save scrolling up:

const stats = [
    'fight',
    'dodge'
];
stats.forEach(button => {
    on(`clicked:${button}`, () => {
    getAttrs([`${button}stat`], v=> {
            startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d20s]]}} {{highest=[[0]]}} {{lowest=[[0]]}}`, (diceroll) => {
                const total = diceroll.results.roll.result
                const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                
                const high = Math.max(...dice);
                const low = Math.min(...dice);
                let score = +v[`${button}stat`] || 0;
                score = score + 1;
                    finishRoll(
                    diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                    {
                        roll: total,
                        highest: high,
                        lowest: low
                    }
                );
                setAttrs({
                    [`${button}stat`]: score
                });
            });
        });
    });
});
March 09 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

RollTemplate logic functions only work with numbers, as you've found.

The simplest way would be to use the stat variable - and have a way to tell which term to use from "fight" or "dodge", then apply the new term to the computer values. There's more than one way to do this, but without knowing the full list of stats you are using, the easiest way is to make the cod esomething you can easily transform yourself. First change the code like this:

const stats = {
            fight: "Fighting",
            dodge: "Ducking"
        };
        Object.keys(stats).forEach(button => {
            on(`clicked:${button}`, () => {
            getAttrs([`${button}stat`], v=> {
                    startRoll(`&{template:diceroll} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[@{${button}}d20s]]}} {{highest=[[0]]}} {{lowest=[[0]]}} {{term[[0]]}}`, (diceroll) => {
                        const term = stats[button];
                        const total = diceroll.results.roll.result
                        const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                       
                        const high = Math.max(...dice);
                        const low = Math.min(...dice);
                        let score = +v[`${button}stat`] || 0;
                            score = score + 1;
                            finishRoll(
                            diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                            {
                                roll: total,
                                highest: high,
                                lowest: low,
                                term: term
                            }
                        );
                        setAttrs({
                            [`${button}stat`]: score
                        });
                    });
                });
            });
        });

This method modifues the code to add an extra key to the roll string ((term=[[0]]}} - this is only used to allow you to save text to it.

Now in the rolltemplate, you can display the word by using computed::term wherever you want.


You can add aother attributes and their terms to the stats variable at the start, in the same format.

March 11 (3 years ago)
Aero
Pro

That's brilliant GiGs, thank you!

Another one for you – how would this sheetworker (either the version in your last post or the format I was working with just before) be tweaked to work with buttons in repeating sections?

Say fight and dodge are both in a fieldset called action. Simply replacing fight with repeating_action_fight doesn't work.

Can it be done?

March 11 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

It can be done, but there are multiple ways it can be done.

If everying affected by the worker is on the same row (the buttons, the attributes you read the values of, and the attribute you save to), then you can probably just replace every instance of

${button}

in the last worker with

repeating_action_${button}

(Except those in the startRoll text - that line should remain unchanged).

To be clear here - leave any button alone, it should be changed only if it has the ${ } around it.

If there's any crossing of the rows, it gets more complicated. It can be done, but the code is trickier, and depends exactly on how the repeating section is written. If this change doesn't work, you'll need to post the repeating section, and describe what attributes are being read and saved.


Also since you keep asking for variations of the code, it's better if you just describe what you want the end solution to be and we can do that instead of continually tweaking code that isn't complete yet.

March 11 (3 years ago)
Aero
Pro

I've found that actually it will work without any repeating_action_ addition at all, except for the roll itself which just comes up with 0 dice whatever the associated attr is, so I guess the getAttrs isn't working. Tried –

getAttrs([`repeating_action_${button}stat`], v=> {

but that didn't help. Then I tried

{{roll=[[@{repeating_action_${button}}d20s]]}}

in the startRoll line just to see and weirdly it rolled a single die, again regardless of the attr's value.

On further testing I found that it does this even for buttons and attrs not in repeating sections. Any idea why it would set the number of dice to 1?


Also since you keep asking for variations of the code, it's better if you just describe what you want the end solution to be and we can do that instead of continually tweaking code that isn't complete yet.

Sorry, yes I know it must be frustrating. It's partly that I like to learn things piecemeal so adapting what I learn is easier, but also that my default process is to build a sheet that will do and then develop it bit by bit to improve user-friendliness so I don't usually have an end vision to provide.

March 11 (3 years ago)
Aero
Pro

Figured it out – the roll was looking for an attr with the same name as the button rather than with the 'stat' suffix attached. I re-named the attrs to match the button names and removed any reference to the 'stat' suffix and the resulting code works.

Still can't figure out how to get it to work with repeating sections though...


<button type="action" type="number" name="act_fight" style="text-align:center;">FIGHT</button>  <input name="attr_fight" type="number" /><br />
<button type="action" type="number" name="act_dodge" style="text-align:center;">DODGE</button>  <input name="attr_dodge" type="number" />
<rolltemplate class="sheet-rolltemplate-diceroll">
    {{name}}<br />
    {{computed::term}}<br />
    {{computed::roll}}<br />
    {{computed::highest}}<br />
    {{computed::lowest}}
</rolltemplate>
<script type="text/worker">
const stats = {
            fight: "Fighting",
            dodge: "Ducking"
        };
        Object.keys(stats).forEach(button => {
            on(`clicked:${button}`, () => {
            getAttrs([`${button}`], v=> {
                    startRoll(`&{template:diceroll} {{name=${button}}} {{roll=[[(@{${button}})d6s]]}} {{highest=[[0]]}} {{lowest=[[0]]}} {{term=[[0]]}}`, (diceroll) => {
                        const term = stats[button];
                        const total = diceroll.results.roll.result
                        const dice = diceroll.results.roll.dice //This will be an array of the values rolled on all the dice
                        
                        const high = Math.max(...dice);
                        const low = Math.min(...dice);
                            finishRoll(
                            diceroll.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.
                            {
                                roll: total,
                                highest: high,
                                lowest: low,
                                term: term
                            }
                        );
                    });
                });
            });
        });
</script>
March 11 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

You need to post the repeating section code. Otherwise we are just flailing in the dark.

March 12 (3 years ago)
Aero
Pro

Beg your pardon –

<fieldset class="repeating_action">
<button type="action" type="number" name="act_fight" style="text-align:center;">FIGHT</button>  <input name="attr_fight" type="number" /><br />
<button type="action" type="number" name="act_dodge" style="text-align:center;">DODGE</button>  <input name="attr_dodge" type="number" />
</fieldset>
March 12 (3 years ago)
GiGs
Pro
Sheet Author
API Scripter

Is that a real repeating section? Can you post your full html (in a pastebin.com) and describe the actual problem you need help with? Don't simplify it for the purpose of asking questions.

March 12 (3 years ago)
Aero
Pro

Well my thinking is if I learn the general case then I can tweak it as needed to the many different repeating sections I have, but if you think it would help to see one then sure thing:

https://pastebin.com/iQQPzpfP

So what I'm after is for the 12 buttons that are just a verb with 'Cap' on the front (so Capfight, Capgrowl, ... , Caphustle, which appear three times each) to trigger the roll such that I can extract the value of the attr by the same name without the 'Cap' (capitalised first letter, so Fight, Growl, ... , Hustle) for the number of dice to roll as well as the name of the attr used for the roll and the entry in the input 'capname'. All these things are coming from the same row in the section.

I've also included, at the bottom, a sheetworker I built using what I learned in this same thread. The sheetworker I'm building for the repeating section needs to do exactly the same as that sheetworker (which refers to the same list of attrs, but those are different and separate to the ones in the repeating section).

(I make no apology for the atrocious quality of coding!)

March 12 (3 years ago)
Aero
Pro

Oh, and the repeating section is in a table (the old/bad kind).