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

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?
1644433072

Edited 1644498956
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).
1644434573
Finderski
Plus
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...
Hmm, maybe time I started looking into this API craze... Cheers GiGs.
1644443237
Finderski
Plus
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...
1644444855

Edited 1644454155
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] }}
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>
1644460763

Edited 1644460805
Finderski
Plus
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...
1644481919

Edited 1693002088
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>
1644513602

Edited 1644572646
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.
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?
1644602754

Edited 1644602860
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.
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>
1644648272
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.
1644648736

Edited 1644648986
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.
1644648874
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.
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!
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?
1644764253
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.
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!
1644780929
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.
1644781245
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.
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                     }                 );             });         }); });
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?
1644789494
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.
1644790493
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.
GiGs said: But its maybe more likely there's something iffy in your rolltemplate. Yep, forgot the computed::  bit AGAIN. Sorry! {{facepalm}}
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!
1644831991
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 :)
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?
1646323501

Edited 1646323985
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.
1646324155
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.
Fantastic, cheers GiGs!
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
1646327893
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: });
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                 });             });         });     }); });
1646332038
GiGs
Pro
Sheet Author
API Scripter
what do do the html for your fightsta and dodgestat  look like?
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.
1646335101

Edited 1646336473
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.
1646842819

Edited 1646842956
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                 });             });         });     }); });
1646852701
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.
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?
1646977654
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.
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.
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>
1647023034
GiGs
Pro
Sheet Author
API Scripter
You need to post the repeating section code. Otherwise we are just flailing in the dark.
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>
1647120246
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.
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: <a href="https://pastebin.com/iQQPzpfP" rel="nofollow">https://pastebin.com/iQQPzpfP</a> 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!)
Oh, and the repeating section is in a table (the old/bad kind).