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

Using Custom Roll Parsing to determine the second highest result

I'm using CRP to perform calculations on a roll. Below is the rolltemplate and sheetworker, which rolls different numbers of different-sized dice up to d10s. Currently it's set to count the total number of dice rolled as well as the frequency of each value occurring and the highest and lowest values rolled. Firstly – it's not working! Can anybody spot what's wrong with it? Secondly – really, what I need it to calculate is the second highest and third highest values rolled. What would the syntax for those functions look like? Rolltemplate: <rolltemplate class="sheet-rolltemplate-fo"> {{PC}} rolls {{name}}<br />{{computed::rolltotal}} </rolltemplate>  Sheetworker: const stats = [     'fallout' ]; fo.forEach(button => {     on(`clicked:${button}`, () => {             startRoll(`&{template:fo} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[[[@{d4s}]]d4s+[[@{d6s}]]d6s+[[@{d8s}]]d8s+[[@{d10s}]]d10s]]}}`, (fo) => {                 const total = fo.results.roll.result                 const dice = fo.results.roll.dice //This will be an array of the values rolled on all the dice                 const total1s = dice.filter(d => d === 1).length;                 const total2s = dice.filter(d => d === 2).length;                 const total3s = dice.filter(d => d === 3).length;                 const total4s = dice.filter(d => d === 4).length;                 const total5s = dice.filter(d => d === 5).length;                 const total6s = dice.filter(d => d === 6).length;                 const total7s = dice.filter(d => d === 7).length;                 const total8s = dice.filter(d => d === 8).length;                 const total9s = dice.filter(d => d === 9).length;                 const total10s = dice.filter(d => d === 10).length;                 const high = Math.max(...dice);                 const low = Math.min(...dice);                                  finishRoll(                     fo.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.                     {                         rolltotal: total,                         one: total1s,                         two: total2s,                         three: total3s,                         four: total4s,                         five: total5s,                         six: total6s,                         seven: total7s,                         eight: total8s,                         nine: total9s,                         ten: total10s                     }                 );             });         }); });
1659844642
GiGs
Pro
Sheet Author
API Scripter
You're calculating a lot of values you're not using in the rolltemplate. Can you describe - in full, the finished result - what you want to display in the rolltemplate? Not what you want to solve right this minute, but the final, end result. Also it looks like you are trying to generate more then 10 calculated results. I seem to remember that it only works for up to 10 results. As an intermediary, you can probably iterate splice to get the highest rolls (untested code): let dice = fo.results.roll.dice: //This will be an array of the values rolled on all the dice const high = dice.splice(Math.max(...dice)); const second = dice.splice(Math.max(...dice)); const third = dice.splice(Math.max(...dice)); splice returns the current high result, and removes that from the array, so in this case the array is shrinking after each entry. You'd need to include some error testing: what if dice is not an array with at least 3 entries, for example.
1659845693

Edited 1659846398
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
GiGs said: Also it looks like you are trying to generate more then 10 calculated results. I seem to remember that it only works for up to 10 results. This was finally fixed thankfully As for your issue Aero, there's a few things I notice. fo.forEach(button => { I'm not sure what fo  is supposed to be here (the only other place I see it is as the argument passed to your startRoll callback), but I'd bet that this is the wrong variable. I highly recommend using descriptive variable names so that you can more easily trace the purpose and/or intended value of a variable. Using descriptive variable names also lets community members like GiGs and I that answer questions more easily tell what a piece of code is supposed to do. Additionally, you must have a field with an existing inline roll in it in order to display a computed roll. So you need placeholder rolls for all the computed values you are passing to finishRoll. And then, just some general code critique, but I much prefer the async/await pattern instead of callback pattern for startRoll. It allows you to nest less, which makes the code cleaner and easier to read. It also lets you more easily react to the results of startRoll and conditionally do other things. I'd rewrite this function like so: const stats = [ 'fallout' ]; fo.forEach(button => {//Replace `fo` with the appropriate variable (maybe it's supposed to be the stats array?) on(`clicked:${button}`, async () => {//Denote our function as an asynchronous function const results = await startRoll(`&{template:fo} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[[[@{d4s}]]d4s+[[@{d6s}]]d6s+[[@{d8s}]]d8s+[[@{d10s}]]d10s]]}} {{rolltotal=[[0[computed value]]]}}`);//Repeat what I've done for `rolltotal` for each of the fields you are trying to compute. const total = results.results.roll.result;//You were also missing several semi-colons. While usually not important, they can really screw with your code. const dice = results.results.roll.dice; //This will be an array of the values rolled on all the dice //Instead of storing each of these in a variable, just make a computeObj object and store them as properties in that. const computeObj = { total1s:dice.filter(d => d === 1).length, total2s:dice.filter(d => d === 2).length, total3s:dice.filter(d => d === 3).length, total4s:dice.filter(d => d === 4).length, total5s:dice.filter(d => d === 5).length, total6s:dice.filter(d => d === 6).length, total7s:dice.filter(d => d === 7).length, total8s:dice.filter(d => d === 8).length, total9s:dice.filter(d => d === 9).length, total10s:dice.filter(d => d === 10).length, }; //I've left high and low out of the computeObj because I'm not sure what you're doing with them. const high = Math.max(...dice); const low = Math.min(...dice); finishRoll( results.rollId, // this is where you save the computed values into something that can be passed to rolltemplates. computeObj//Then just pass the computeObj as the second argument of finishRoll ); }); }); By switching to the async/await pattern, renaming the return value of startRoll  to something that describes it's function, and using a computeObj , the code is now much more streamlined and easier to read; all without any change to the performance. EDIT: We can actually streamline this code even more by using the reduce  array method: const stats = [ 'fallout' ]; fo.forEach(button => {//Replace `fo` with the appropriate variable (maybe it's supposed to be the stats array?) on(`clicked:${button}`, async () => {//Denote our function as an asynchronous function const results = await startRoll(`&{template:fo} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[[[@{d4s}]]d4s+[[@{d6s}]]d6s+[[@{d8s}]]d8s+[[@{d10s}]]d10s]]}} {{rolltotal=[[0[computed value]]]}}`);//Repeat what I've done for `rolltotal` for each of the fields you are trying to compute. const total = results.results.roll.result const dice = results.results.roll.dice //This will be an array of the values rolled on all the dice //Instead of storing each of these in a variable, just make a computeObj object and store them as properties in that. const computeObj = [1,2,3,4,5,6,7,8,9,10].reduce((memo,val)=>{//This does the exact same thing as the computeObj definition in my original version above memo[`total${val}s`] = dice.filter(d => d === val).length; return memo; },{});//End assignment of computeObj //I've left high and low out of the computeObj because I'm not sure what you're doing with them. const high = Math.max(...dice); const low = Math.min(...dice); finishRoll( results.rollId, // this is where you save the computed values into something that can be passed to rolltemplates. computeObj//Then just pass the computeObj as the second argument of finishRoll ); }); }); EDIT2: Realized I hadn't replaced all the references to fo  in the startRoll related lines.
1659846360
GiGs
Pro
Sheet Author
API Scripter
Scott C. said: GiGs said: Also it looks like you are trying to generate more then 10 calculated results. I seem to remember that it only works for up to 10 results. This was finally fixed thankfully There's no limit any more?
1659846408

Edited 1659846440
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Shouldn't be, if there is, then it's a return of the bug, which should be reported again as such.
1659846712
GiGs
Pro
Sheet Author
API Scripter
Scott C. said: finishRoll( fo.rollId, // this is where you save the computed values into something that can be passed to rolltemplates. computeObj//Then just pass the computeObj as the second argument of finishRoll ); Sorry Aero for hijacking yout thread again. Scott can you pass an array or object here (computeObj), since it'll be going to a rolltemplate? Can you use full javascript variables in a rollTemplate? I assumed that you were limited to single item values like strings, integers, and floats (though it occurs to me that was an assumption for use with rollTemplate helpers, and I can see you could probably use array or object keys with those).
1659846740
GiGs
Pro
Sheet Author
API Scripter
Scott C. said: Shouldn't be, if there is, then it's a return of the bug, which should be reported again as such. That's good to know. I hadnt seen any notice of the change.
1659847678

Edited 1659847703
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
GiGs said: Scott C. said: finishRoll( fo.rollId, // this is where you save the computed values into something that can be passed to rolltemplates. computeObj//Then just pass the computeObj as the second argument of finishRoll ); Sorry Aero for hijacking yout thread again. Scott can you pass an array or object here (computeObj), since it'll be going to a rolltemplate? Can you use full javascript variables in a rollTemplate? I assumed that you were limited to single item values like strings, integers, and floats (though it occurs to me that was an assumption for use with rollTemplate helpers, and I can see you could probably use array or object keys with those). The values of the object's properties can be strings or numbers. Objects will show up as [object object]. Not sure what would happen if you passed an array as a property's value. Essentially the object works the same as the object that we pass to setAttrs.
'fo' was just a name for the variable (if that's what it is) – I had adapted a sheetworker from another sheet I'd previously built and swapped out the names, but got confused about what needed to match what. I've matched the first two instances as 'stats' and added the {{rolltotal=[[0]]}} that I'd forgotten in the roll formula and now the roll is functioning. Thanks! However I still can't get the highest, second highest and third highest values calculated. Here's what I have: <rolltemplate class="sheet-rolltemplate-fo"> {{PC}} rolls {{name}}<br /> {{computed::rolltotal}}<br /> {{computed::highest}}<br /> {{computed::highest2}}<br /> {{computed::highest3}}     </rolltemplate>  const stats = [     'fallout' ]; stats.forEach(button => {     on(`clicked:${button}`, () => {             startRoll(`&{template:fo} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[[[@{d4s}]]d4s+[[@{d6s}]]d6s+[[@{d8s}]]d8s+[[@{d10s}]]d10s]]}} {{rolltotal=[[0]]}} {{highest=[[0]]}} {{highest2=[[0]]}} {{highest3=[[0]]}}`, (fo) => {                 const total = fo.results.roll.result                 let dice = fo.results.roll.dice //This will be an array of the values rolled on all the dice                 const high = dice.splice(Math.max(...dice));                 const second = dice.splice(Math.max(...dice));                 const third = dice.splice(Math.max(...dice));                 finishRoll(                     fo.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.                     {                         rolltotal: total,                         highest: high,                         highest2: second,                         highest3: third,                     }                 );             });         }); }); This rolls and returns name, PC and rolltotal, but empty entries for highest, highest2 and highest3. Any further guidance would be much appreciated, whether using this format or the one Scott C provided.
Now that I think about it, I will want it to perform a second roll simultaneously of the form {{roll2=[[[[@{body}]]d6]]}} and will need a rolltotal, highest, highest2 and highest3 for this roll as well as the first. I assume I can just duplicate all the parts and give them different names (since that worked on my last CRP project), but please advise if this won't work here for some reason. For clarity... – I need the calculations of highest, highest2 and highest3 to not skip duplicates. I.e. Of (8,6,6,4) highest3 should return 6, not 4. – The first roll won't ever have fewer than three dice so highest3 should always have a result. The second roll might have only two dice, but so long as this doesn't make the roll fail it doesn't matter what highest3 returns in that situation.
I've actually worked it out!! I now have a sheet worker that will roll given quantities of d4s, d6s, d8s and d10s, take the highest two values (not skipping duplicates) and return them individually as well as their sum. Simultaneously it's rolling a given number of d6s, taking the highest three values and returning them individually as well as their sum. However, this fails if there are fewer than three dice so I added an extra sum of just the highest two values (there won't be less than two dice rolled) and left it for the rolltemplate to work out which one to display according to the number of dice being rolled. The sorting I did using a sort function on the arrays rather than in the roll code. I used splice to find the highest as GiGs suggested, but now that the arrays are in ascending order I could just have each one remove the last entry before taking the maximum. Weirdly this is required for the first check, which you would have thought wouldn't want to remove any elements from the array before taking a maximum – not quite sure why that is, but it's definitely required rather than a simple maximum on the original array. Here's the result: <rolltemplate class="sheet-rolltemplate-fo"> {{PC}} rolls {{name}}<br /> Fallout total:{{computed::rolltotal}}<br /> {{computed::highest1}}<br /> {{computed::highest2}} <br /><br /> {{#rollGreater() computed::dicecount2 2}}     Body:{{computed::rolltotal2}}<br />     {{computed::highest12}}<br />     {{computed::highest22}}<br />     {{computed::highest32}} {{/rollGreater() computed::dicecount2 2}} {{#^rollGreater() computed::dicecount2 2}}     Body:{{computed::partrolltotal2}}<br />     {{computed::highest12}}<br />     {{computed::highest22}} {{/^rollGreater() computed::dicecount2 2}}     </rolltemplate>  const stats = [     'fallout' ]; stats.forEach(button => {     on(`clicked:${button}`, () => {             startRoll(`&{template:fo} {{name=${button}}} {{PC=@{character_name}}} {{roll=[[[[@{d4s}]]d4+[[@{d6s}]]d6+[[@{d8s}]]d8+[[@{d10s}]]d10]]}} {{roll2=[[[[@{body}]]d6]]}} {{rolltotal=[[0]]}} {{highest1=[[0]]}} {{highest2=[[0]]}} {{highest3=[[0]]}} {{dicecount=[[0]]}} {{rolltotal2=[[0]]}} {{highest12=[[0]]}} {{highest22=[[0]]}} {{highest32=[[0]]}} {{dicecount2=[[0]]}} {{partrolltotal2=[[0]]}}`, (fo) => {                 const total = fo.results.roll.result                 const dice = fo.results.roll.dice.sort(function(a, b){return a - b}); //This will be an array of the values rolled on all the dice                 const total2 = fo.results.roll2.result                 const dice2 = fo.results.roll2.dice.sort(function(a, b){return a - b}); //This will be an array of the values rolled on all the dice                 const count = dice.filter(d => d > 0).length;                 const high = Math.max(...dice.splice(-1 , 1));                 const second = Math.max(...dice.splice(-1 , 1));                 const third = Math.max(...dice.splice(-1 , 1));                 const sum = high + second;                 const count2 = dice2.filter(d => d > 0).length;                 const high2 = Math.max(...dice2.splice(-1 , 1));                 const second2 = Math.max(...dice2.splice(-1 , 1));                 const third2 = Math.max(...dice2.splice(-1 , 1));                 const sum2 = high2 + second2 + third2;                 const partsum2 = high2 + second2;                 finishRoll(                     fo.rollId, // this is where you save the computed values into something that can be passed to rolltemplates.                     {                         rolltotal: sum,                         highest1: high,                         highest2: second,                         highest3: third,                         dicecount: count,                         rolltotal2: sum2,                         partrolltotal2: partsum2,                         highest12: high2,                         highest22: second2,                         highest32: third2,                         dicecount2: count2,                     }                 );             });         }); }); Thanks again for all your help!!
1659928256
GiGs
Pro
Sheet Author
API Scripter
Congrats. If the arrays are in a specific order, you can use dice[0], dice [1], dice [2] to get the first, second, and third item, or if they ordered with the highest at the end, you can use the length property: dice[dice.length - 1], dice[dice.length - 2], dice[dice.length - 3] These don't change the original array in any way. I mention it for the future - in some routines you'll want to extract values from the array, but then use the unmodifued array again.