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

Obtaining Individual Dice from a Dice Roll

Hi there, I've got quite a specific-use question here; I'm working with a system where the initial attack roll is a 3d6 (with modifiers or advantage, etc). However, after the attack roll, the damage roll takes a particular dice from the actual attack roll itself - for example, for a weapon with advantage and a +3 mod under the 'High' damage category: 4d6dl1+3 = ( 5 + 6 + 1 + 4 ) + 3 --> 15 Damage: High damage dice, so 6 - if it was mid, it would be 5 , and low would be 4 . Is there any way to extract these individual dice rolls from the first roll, and put them into a damage equation afterward?
1614524087

Edited 1614524597
Oosh
Sheet Author
API Scripter
You'll need to use an API script to be able to grab the right roll and have it totaled properly for the attack. AFAIK the 3d dice are still broken when rolling from the API, so if those are important to you, you'll probably need to set up a reactive script instead, with some kind of keyword or the name of the roll template you're using as a trigger, then use normal macros to roll. Here's a quick example. The API script: on('ready', () => { on('chat:message', (msg) => { if (msg.inlinerolls && msg.content.match(/weapon: (high|mid|low)/i)) { let weaponType = msg.content.match(/weapon: (high|mid|low)/i)[1]; let atkRoll = msg.inlinerolls.find(r => r.expression.match(/(\dd6)/i)); if (!atkRoll) {sendChat('Roller', `Couldn't find valid Xd6 attack roll`); return} let atkDice = atkRoll.results.rolls[0].results .filter(res=>!res.d) .map(res=>res.v) .sort((a,b) => b-a); log(atkDice); if (atkDice.length < 3) {sendChat('Roller', `A minimum of 3 dice must be kept!`); return} let dmgIndex = (/high/i.test(weaponType)) ? 0 : (/mid/i.test(weaponType)) ? 1 : (/low/i.test(weaponType)) ? 2 : null; if (isNaN(dmgIndex)) {sendChat('Roller', `Unexpected weapon damage category!`); return} sendChat('Roller', `Damage die was ${atkDice[dmgIndex]}!`); } }); }); This listens for a roll with the string weapon: high/mid/low in it and determines the correct die. So this macro entered into chat: &{template:default} {{name=Attack Roll}} {{Weapon: low damage}} {{Attack=[[4d6dl1+5]]}} This will roll the attack and the script will spit out the lowest die roll. It's pretty basic, but the script can easily spit out a nicer looking template or a clickable damage button. edit - added a couple of checks to the script.... roll detection is a bit basic, a roll of [[1d6 + 2d6]] instead of [[3d6]] will confuse it
Yer a wizard, Oosh. Exactly what I was looking for! I do have a few questions about your arcane scripts, though; I can make out the checks you've put in and understand those, and I... think I vaguely understand how you're getting the results? The issue I'm having understanding is I admittedly have no idea how you're actually getting those dice rolls; I'm not familiar enough to understand what's going on with the .filter, .map and .sort which seems to be doing it there. I'm currently trying to edit it to allow the 'extreme' damage die, which is a rare-case scenario we have which uses mid+high die, but I'm fairly sure I just don't understand what's going on enough for it to work and butchered it a little: on('ready', () => {     on('chat:message', (msg) => {         if (msg.inlinerolls && msg.content.match(/weapon: (|high|mid|low|extreme)/i)) {             let weaponType = msg.content.match(/weapon: (high|mid|low|extreme)/i)[1];             let atkRoll = msg.inlinerolls.find(r => r.expression.match(/(\dd6)/i));             if (!atkRoll) {sendChat('Roller', `Couldn't find valid Xd6 attack roll`); return}             let atkDice = atkRoll.results.rolls[0].results                 .filter(res=>!res.d)                 .map(res=>res.v)                 .sort((a,b) => b-a ==> b+a);             log(atkDice);             if (atkDice.length < 3) {sendChat('Roller', `A minimum of 3 dice must be kept!`); return}             let dmgIndex = (/high/i.test(weaponType)) ? 0 : (/mid/i.test(weaponType)) ? 1 : (/low/i.test(weaponType)) ? 2 : (/extreme/i.test(weaponType)) ? 3 : null;             if (isNaN(dmgIndex)) {sendChat('Roller', `Unexpected weapon damage category!`); return}             sendChat('Roller', `Damage die was ${atkDice[dmgIndex]}!`);         }     }); }); This was my attempt, but I'm fairly sure I'm missing a big step in the process. The other thing I wanted to ask was if it was possible to do further manipulation of the die once the number's given out; is it possible to take the spat out variable in the chat and further manipulate it automatically, or is that something that'd needed to be done during the script itself? In which case, I'm sure it'd be possible to add in a 'melee' 'ranged' etc tag to it, which I'm sure I can work out, but I was wondering if simple manipulation afterward would be possible rather than tying it directly to the script itself. Thank you for your help!
1614567175
David M.
Pro
API Scripter
You may also want to check out the Scriptcards api script. You can do all kinds of stuff with that, including re-using rolls (with extra math operations), use conditionals, create functions, call other api scripts, etc. A single script install can give a whole lot of flexibility, without having to learning any javascript.
1614568475

Edited 1614568828
Oosh
Sheet Author
API Scripter
edit - or use Scriptcards :) Sure, so the inline rolls in a chat message are stored in the message object which is what triggers the API's on('chat:message') listener, and we're passing that to the rest of the script as the parameter (msg). More information on the chat message event here , you'll need to click the 'expand' button to see the example inlinerolls object. It's an array of data, essentially one for each roll as the parser breaks it up, though each roll can be split into sub rolls (like the [[1d6 + 2d6]] example above) which further complicates things. You can deal with these subrolls with another level of iteration, but if you're not planning on using rolls like that there's not much point adding the complexity. What this part does:             let atkRoll = msg.inlinerolls.find(r => r.expression.match(/(\dd6)/i));             if (!atkRoll) {sendChat('Roller', `Couldn't find valid Xd6 attack roll`); return}             let atkDice = atkRoll.results.rolls[0].results                 .filter(res=>!res.d)                 .map(res=>res.v)                 .sort((a,b) => b-a ==> b+a); The first line is finding the first roll at the top level of the array with an expression matching <digit>d6. If you have multiple d6 rolls and the attack roll is not the first one, this is going to grab the wrong roll and would need to be refined a bit. The next line is just a check to make sure we don't get errors further down if atkRoll is empty. The following part will make more sense if you check out the example roll object. Another good way to check it out is to add a log line in there: on('ready', () => { on('chat:message', (msg) => { if (msg.inlinerolls && msg.content.match(/weapon: (high|mid|low)/i)) { log(msg.inlinesrolls); ... This will throw the rolls into the API log each time the script is triggered. Unfortunately it's not formatted at all in the API log - you can either copy & paste it into an IDE, or open your browser console with F12, and paste it into the command console.log(...paste...), then it'll be formatted as an array of objects with drop-down arrows to explore. So those last few lines are first grabbing the rolls from the  way the roll data is structured with results.rolls[0].results - rolls[0] is grabbing the first (zero-indexed) subroll from the array, and the second "results" is yet another array of the individual die results of that subroll. This is an example of what that might look like: "results": [ {"v": 6}, {"v": 5}, {"v": 6}, {"v": 3, "d": true} ] So the filter function removes any object which has a key called 'd' and has a truthy value. This is how a dropped die from "4d6dl1" is stored, so it'll filter those out. We should then have an array like this: "results": [{"v": 6}, {"v": 5}, {"v": 6}] The map function grabs the value of the 'v' key from the object - this is just cleaning up the data so we've got a simple array of numbers instead of key/value pairs. So now we have this: [6,5,6] The sort function then sorts an array depending on the compare function provided. I've used shorthand syntax there, but it's a function with 2 parameters which sorts two values depending on whether the function returns a negative, positive or zero result. That (a,b) => b-a   is really function(a,b) {return b - a} , which means if a is bigger than b , sort a first - descending order. For a normal ascending order, you'd use a - b . So now we have [6,6,5] - provided there are 3 dice in there, we know that index 0 is the highest (or equal highest, the sort function does nothing for a zero-result), index 1 is the middle and index 2 is the lowest (or, again, equal). So if you want the extreme version in there, there's a few ways to go about that. The dmgIndex line is another syntactic shortcut called a ternary, it's basically a bunch of nested if() statements, and probably not ideal for understanding what's going on. It'll make more sense as traditional if statements: let damage; if (/extreme/i.test(weaponType)) damage = atkDice[0] + atkDice[1]; else if (/high/i.test(weaponType)) damage = atkDice[0]; else if (/mid/i.test(weaponType)) damage = atkDice[1]; else if (/low/i.test(weaponType)) damage = atkDice[2]; else {sendChat('Roller', `Unexpected weapon damage category!`); return} So there's a couple of issues with what you've tried above - firstly there's an extra | pipe in the regular expression at the top which is going to let everything containing "weapon: " through, as it's included an empty string as a match option, and a string is made up of an infinite number of empty strings. I won't go into regular expressions, but here's a link if you're interested. They're very useful for searching for triggers, as you can make them case insensitive and be as specific or vague as you like in how or where the characters appear in the input. The addition to the sort() function isn't legal either, the => fat arrow syntax is a way of declaring a function, but you can't add ==> one of these on the end. In terms of doing more things with the number once you've extracted it: absolutely. You can easily do whatever you want inside the script, but since this is tabletop and not a video game, you generally want to make it visible to the players. You're free to throw the numbers back to the chat parser via the sendChat() function, as all the Javascript variables will be replaced by numbers/strings/whatever you have stored in there, before Roll20 sees it again. Here's an updated version with the 'extreme' tag, a fancied up output (with some totally made up damage rules as an example), and relaxed the weapon search term to it doesn't need a colon or space after it, just any of the categories appearing somewhere after the word 'weapon' in the same chat message will do: on('ready', () => { on('chat:message', (msg) => { if (msg.inlinerolls && msg.content.match(/weapon.*(extreme|high|mid|low)/i)) { let weaponType = msg.content.match(/weapon.*(extreme|high|mid|low)/i)[1]; let atkRoll = msg.inlinerolls.find(r => r.expression.match(/(\dd6)/i)); if (!atkRoll) {sendChat('Roller', `Couldn't find valid Xd6 attack roll`); return} let atkDice = atkRoll.results.rolls[0].results .filter(res=>!res.d) .map(res=>res.v) .sort((a,b) => b-a); log(atkDice); if (atkDice.length < 3) {sendChat('Roller', `A minimum of 3 dice must be rolled/kept!`); return} let damage; if (/extreme/i.test(weaponType)) damage = atkDice[0] + atkDice[1]; else if (/high/i.test(weaponType)) damage = atkDice[0]; else if (/mid/i.test(weaponType)) damage = atkDice[1]; else if (/low/i.test(weaponType)) damage = atkDice[2]; else {sendChat('Roller', `Unexpected weapon damage category!`); return} sendChat('Roller', `&{template:default} {{name=Damage Roll}} {{Damage=Double damage (crit) + Bless<br>[[2*${damage}[${weaponType}]+1d4[Bless]]]!}}`); } }); });
Thanks to both of you! I think it'll take me some time to chew through your explanation, Oosh, but phrasing it as 'if' statements made it a hell of a lot clearer to me, have to admit! Made much more sense to me that way; thank you! In response to David; ScriptCards looks like it could also be a useful way to go about it! They seem like they have a variety of other uses I could utilise for this setting too. If nothing else, the branches seem like a very easy way of adding in critical hits and such! In the interest of learning more, I gave it a quick try; I can see how the dice rolls are stored and you can call on them later, but I'm having issues manipulating the individual rolls further. For example, 'PlayerCharacter' decides to do a 'Basic Attack' with 'Weapon'. They have advantage with this weapon, so it rolls 4 and keeps the 3 highest. !scriptcards {{ --#title|PlayerCharacter Character name (will use 'selected' to reference token name instead later) --#leftsub|Weapon Weapon name --#rightsub| ?{AttackType?|Basic Attack |Special Attack } Type of attack --=AttackRoll|4d6kh3 Attack roll itself --+Attack|[$AttackRoll] Attack roll display - this works fine! --=Damage1|[$AttackRoll.Text]kl2 Converting to middle die part 1 - this is the part it seems to fail at! --=Damage2|[$Damage2.Text]kh1 Part 2 --+Damage|[$Damage2] Displaying damage dice - ends up being a higher number than the actual attack roll? }}
1614624473
David M.
Pro
API Scripter
Sam, I am now kinda regretting mentioning scriptcards for your application (lol) as now that I looked into it deeper your particular case is actually fairly complicated. Getting min and max rolls is pretty easy, though you would have to have explicit die rolls rather than using the kh3 notation, and then use conditionals etc. However, to get the mid value will require a sorting operation, which scriptcards does not natively support at this moment. I tried using a classic bubble sort algorithm but ran into some issues which I won't go into here. If you're interested, I started a discussion on the scriptcards thread here . Definitely not "Intro to Scriptcards 101" material! It's also possible that there is a simpler approach than what I was trying. So until this is resolved or until there is some kind of native roll sorting support for scriptcards, Oosh's clean one-off script is definitely going to be the best solution. I'll keep you in the loop if anything comes out of the scriptcards discussion.
Hi David, Not to worry! I might have a fiddle around with it myself; I've been working on getting a script running that can work for our setting very well at any rate. ScriptCards works very well for setting multiple 'modifiers' to the attack at any rate, and works well with our system's method of precision damage and such, so it was a useful resource either way! The conditional branches in particular are amazing in ScriptCards, so at the very least I'm going to thoroughly be using those. I'm having a look over what you've posted and come up with a... admittedly, brute force solution! !scriptcards {{  --=Roll1|4 --=Roll2|2 --=Roll3|3 --=Roll4|1 --=Roll5|5 --:Sorter| --?[$Roll1] -gt [$Roll2]|Swap12 --?[$Roll2] -gt [$Roll3]|Swap23 --?[$Roll3] -gt [$Roll4]|Swap34 --?[$Roll4] -gt [$Roll5]|Swap45 --^SorterFinished| --:Swap12| --=Roll1Temp|[$Roll1] --=Roll2Temp|[$Roll2] --=Roll1|[$Roll2Temp] --=Roll2|[$Roll1Temp] --^Sorter| --:Swap23| --=Roll2Temp|[$Roll2] --=Roll3Temp|[$Roll3] --=Roll2|[$Roll3Temp] --=Roll3|[$Roll2Temp] --^Sorter| --:Swap34| --=Roll3Temp|[$Roll3] --=Roll4Temp|[$Roll4] --=Roll3|[$Roll4Temp] --=Roll4|[$Roll3Temp] --^Sorter| --:Swap45| --=Roll4Temp|[$Roll4] --=Roll5Temp|[$Roll5] --=Roll4|[$Roll5Temp] --=Roll5|[$Roll4Temp] --^Sorter| --:SorterFinished| --+Dice|[$Roll1][$Roll2][$Roll3][$Roll4][$Roll5] }}
1614657828
David M.
Pro
API Scripter
Nice, Sam! Nothing wrong with brute force, as long as it works. FYI, I figured out the bubble sort. I had all kinds of syntax errors and logic in my original version that I had to work through.  !scriptcards {{ --=NumDice|4 --=Roll1|1d6 --=Roll2|1d6 --=Roll3|1d6 --=Roll4|1d6 --+Unsorted Rolls| --+Rolls|[$Roll1] [$Roll2] [$Roll3] [$Roll4] --:PERFORM A BUBBLE SORT| --=i|0 --=max_i|[$NumDice]-1 --:OuterLoop| --=i|[$i]+1 --=j|[$i] --:InnerLoop| --=j|[$j]+1 --?[$Roll[$i.Total]] -gt [$Roll[$j.Total]] |>BubbleUp;[$i.Total];[$j.Total] --?[$j.Total] -lt [$NumDice.Total]|InnerLoop --?[$i.Total] -lt [$max_i.Total]|OuterLoop --+Rolls sorted ascending| --+Rolls|[$Roll1] [$Roll2] [$Roll3] [$Roll4] --X| --:FUNCTIONS| --:BubbleUp| accepts i, j as parameters. Swaps Roll[i] & Roll[j] --=Temp|[$Roll[%2%]] --=Roll[%2%]|[$Roll[%1%]] --=Roll[%1%]|[$Temp] --<| }}
1614661237

Edited 1614661337
David M.
Pro
API Scripter
So something like this seems like it would work for your system? !scriptcards {{ --#title|Attack Roll --#rightsub|?{AttackType?|Basic Attack|Special Attack} --&DamType|?{Weapon Damage Type?|Low,Low|Med,Med|High,High} --#leftsub|Weapon ([&DamType]) --=NumDice|?{Roll with Advantage?|No,3|Yes,4} --=Roll1|1d6 --=Roll2|1d6 --=Roll3|1d6 --?[$NumDice.Total] -eq 3|DoneRolling --=Roll4|1d6 --:DoneRolling| --:PERFORM A BUBBLE SORT| --=i|0 --=max_i|[$NumDice]-1 --:OuterLoop| --=i|[$i]+1 --=j|[$i] --:InnerLoop| --=j|[$j]+1 --?[$Roll[$i.Total]] -gt [$Roll[$j.Total]] |>BubbleUp;[$i.Total];[$j.Total] --?[$j.Total] -lt [$NumDice.Total]|InnerLoop --?[$i.Total] -lt [$max_i.Total]|OuterLoop --:OUTPUT ATTACK ROLL| --?[$NumDice.Total] -eq 3|>AttackNorm|>AttackAdv --+[#990000]Attack Roll = [/#]|[$Attack] --:DETERMINE DAMAGE FROM WPN TYPE AND NORM/ADV ROLL| --?[&DamType] -inc "Low" -and [$NumDice] -eq 3|>SetDamage;1 --?[&DamType] -inc "Low" -and [$NumDice] -eq 4|>SetDamage;2 --?[&DamType] -inc "Med" -and [$NumDice] -eq 3|>SetDamage;2 --?[&DamType] -inc "Med" -and [$NumDice] -eq 4|>SetDamage;3 --?[&DamType] -inc "High" -and [$NumDice] -eq 3|>SetDamage;3 --?[&DamType] -inc "High" -and [$NumDice] -eq 4|>SetDamage;4 --+[#990000]Damage = [/#]|[$Damage] --X|End Macro --:FUNCTIONS| --:BubbleUp| accepts i, j as parameters. Swaps Roll[i] & Roll[j] --=Temp|[$Roll[%2%]] --=Roll[%2%]|[$Roll[%1%]] --=Roll[%1%]|[$Temp] --<| --:AttackNorm| --+Rolls|[$Roll1] [$Roll2] [$Roll3] --=Attack|[$Roll1] + [$Roll2] + [$Roll3] --<| --:AttackAdv| --+Rolls| [$Roll2] [$Roll3] [$Roll4] --+[i](Dropped Roll)[/i]|[$Roll1] --=Attack|[$Roll2.Total] + [$Roll3.Total] + [$Roll4.Total] --<| --:SetDamage| accepts the index of the roll to use for damage --=Damage|[$Roll[%1%]] --<| }} Sample output for Med damage wpn with advantage:
Hi David, Yes, something along those lines would be great! However, have to admit - may have jumped the gun, gone into a bit of a coding fugue, and come out the over end several days later with several hours missed sleep and several hundred lines of code. If you want to see the abomination that is the mostly-complete attack script for example character 'Rose', here's a pastebin Thank you for all your help along the way! Wouldn't have gotten here without your and Oosh's help :D
1614810037
David M.
Pro
API Scripter
=O