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

Macro curiosities, or how to overcomplicate everything

The macro parser has a variety of odd mechanisms that can be used for advanced behaviours. The techniques described here are complicated, fragile and shouldn’t be attempted lightly. If you think one of these could help you with your macro needs my recommendation is to try without. If you fail to find an alternative way to achieve what you want then and only then you should try to find an alternative way a second time. If you do need assistance in using one of these techniques - create a new thread to ask where more people will see it and can potentially offer simpler solutions, you'll also receive notifications when someone does answer. In this thread I’ll be covering Parser halting and conditional calculations How roll indices are assigned and resolved Roll index manipulation Accessing the plain numeric roll result Dynamic roll indices Improved roll index manipulation and roll result duplication
Parser halting and conditional calculations When parsing inline rolls certain strings will cause the parser to halt, preventing parts of the roll to be interpreted. Ones I’ve found are:- Double negative numbers Leading negative dice amount Second decimal place Decimal dice amount But notably all three of these can be invoked as the result of a nested inline roll (the choosing roll) and as such used to create a conditional calculation. Examples for conditional +100 Macro text Equivalent macro 1 Equivalent macro 2 Double negative [[ 100 -[[1d2-2]] ]] [[ 100 --1 ]] Returns 0 [[ 100 -0 ]] Returns 100 Leading negative dice amount [[ [[1d2-2]]d1 + 100 ]] [[ -1d1 + 100 ]] Returns -1 [[ 0d1 + 100 ]] Returns 100 Second decimal place [[ 0 + [[1d2/2]].0 + 100 ]] [[ 0 + 0.5.0 + 100 ]] Returns 0.5 [[ 0 + 1.0 + 100 ]] Returns 101 Decimal dice amount [[ 0 + [[1d2/2]]d1 + 100 ]] [[ 0 + 0.5d1 + 100 ]] Returns 0.5 [[ 0 + 1d1 + 100 ]] Returns 101 They all have slightly different properties Double negative will either Return zero Or the entire expression including the subtraction of the choosing roll Oddly though any dice expressions will still be rolled even if their outcomes aren’t used. This results in a failed roll verification. Also note that this can nullify the entire expression. Leading negative dice amount will either Return a result of the choosing roll (which is negative) without rolling dice Or the entire expression including the rolling of the dice based on the choosing roll This has the caveat that it has to be at the start of roll since 0-1d1 would subtract 1d1 from 0 instead of halting the parser. Second decimal place will either Return up to and including the fractional result of the choosing roll Or the entire expression Decimal dice amount will either Return up to and including the fractional result of the choosing roll Or the entire expression including the rolling of the dice based on the choosing roll Things to note with the exception of Double negative is that any dice after the halting point will only be rolled only if the choosing roll ‘selects’ to continue. This has a consequence to be aware of that the inline roll might sometimes be a calculation or sometimes a random roll. Also the parser halts that use dice expressions can also use rollable tables
How roll indices are assigned and resolved How an inline roll gets it’s index is intertwined with how inline rolls get sent to the roll server. From what I’ve been able to deduce roll resolution loops over three phases Search , Request , Resolve Search : The parser searches the macro text from left to right until it finds an inline roll that is ‘completable’. A completable roll is one without any nested rolls, or whose nested rolls have been resolved. Once found it is assigned an incrementing index starting at 0 and added to a list of rolls to request. At this point if the newly indexed roll wasn’t nested the search continues however if the newly indexed roll was a nested roll search stops and moves onto the Request phase. If no further ‘completable’ rolls were found the parser still moves onto the Request phase. Request : The parser checks its list of rolls to request and its list of roll results. If it has results that haven’t been resolved or no requests to make, it makes no request. If there are rolls to request but no results that are awaiting resolution all the rolls on the request list will be sent to the roll server. However, this behaviour is different based on if the inline roll is a calculation (eq [[ {2,4}k1 + 3 ]] ) or a random roll (eq [[ 1d6 + 1t[table] ]]). Only random rolls actually get sent to the roll server, calculations are put onto the list of results immediately. So no requests will be made whilst there are unresolved calculations. Whether a request was made or not, the parser moves onto the Resolve phase. Resolve : The parser will resolve the lowest indexed unresolved result. If this was for an unnested inline roll nothing happens, if this was a nested inline roll it will replace the roll with the numeric result. In either case as long as a result was resolved the parser will move back to the Search phase, otherwise roll resolution ends. Some examples [[0]] [[1]] Search : All rolls are assigned an index Request: No rolls to request, 2 results available Resolve: Index 0 is resolved, no replacement Search : Nothing found Request: No rolls to request, 1 result available Resolve: Index 1 is resolved, no replacement Search : Nothing found Request: No requests or results Resolve: Nothing to do, end [[1d4 index 0]] [[ [[4 index 1]] + 1d6 index 2]] Search : Find indices 0, 1. 1 is nested Request: Index 1 is calc, result is available Resolve: Replace index 1 with numeric result Search : Find index 2 Request: Request indices 0,2 Resolve: Resolve index 0, no replacement Search : Nothing found Request: Results available Resolve: Resolve index 2, no replacement Search : Nothing found Request: No requests or results Resolve: Nothing to do, end [[0 index 0]] [[ [[1d4 index 1]] + 3 index 3]] [[2d1 index 2]] Search : Find indices 0, 1. 1 is nested Request: Index 0 is calc, result available Resolve: Resolve index 0, no replacement Search : [[ [[1d4 index 1]] + 3 index 3]] is not completable find [[2d1 index 2]] Request: Request [[1d4 index 1]] and [[2d1 index 2]] Resolve: Index 1, replace with numeric results Search : Find index 3 Request: Have result indices 2, 3. 3 is calc Resolve: Resolve index 2, no replacement Search : Nothing found Request: have results Resolve: Resolve index 3, no replacement Search : Nothing found Request: No requests or results Resolve: Nothing to do, end
Roll index manipulation As I noted in the parser halting section, using them has the potential consequence that an inline roll is sometimes a calculation and sometimes a random roll. Now with the knowledge that calculations and random rolls are indexed and resolved differently it becomes possible to use conditional calculations to manipulate the order in which inline rolls get assigned their indices &{template:default} [[ [[ {[[1d10 Raw roll]],6}<5 * -1 Choosing Roll]]d1 ]] [[ [[0]] ]] [[1d6+3 High outcome]] [[1d4+2 Low outcome]] {{Raw Roll=$[[0]]}} {{Outcome=$[[5]]}} When the choosing roll is negative, the outer roll is a calculation so gets resolved before the nested zero thus the roll search continues past the nested zero before it can be resolved When the choosing roll is positive, the outer roll is a random roll so the nested zero gets resolved first and that causes the roll around it to get assigned an index before the remaining rolls. This increases their indices by 1. This means the roll you reference can change. One potent aspect of this is that multiple results can stem from the same ‘choice’ &{template:default} [[ [[ {[[1d10 Raw roll]],6}<5 * -1 Choosing Roll]]d1 ]] [[ [[0]] ]] [[1d6+3 High outcome one]] [[1d4+2 Low outcome one]] [[2d6+3 High outcome two]] [[2d4+2 Low outcome two]] {{Raw Roll=$[[0]]}} {{Outcomes=$[[5]] $[[7]]}} So far this has only been increasing an index by 1. But higher amounts are possible however they become more convoluted. &{template:default} [[0d0]] [[ [[ [[ {[[1d10cs>8cf<3 Raw roll]],6}>8f<3 Choosing Roll]]d1 - 1]]d1 ]] [[ [[ [[0d0]]d0 ]] ]] [[ [[0]] ]] [[321 High outcome]] [[210 Mid outcome]] [[109 Low outcome]] {{Raw Roll=$[[1]]}} {{Outcomes=$[[9]]}} Handling 3 options involves bouncing between two nested roll structures with calculation rolls acting as a short circuit to escape. Not to be handled lightly and is rather heavy (and thus slow) on the number of separate requests to the roll server
Accessing the plain numeric roll result Getting the plain numerical result out of a roll is something that people stumble across accidentally in a few ways. Either from reusing rolls out of order or by adding extra square brackets / link markdown. So here I’m gonna try and explain how it happens and how to use it intentionally. Within Overwriting template fields in macros link markdown is used to coerce a roll into a form that allows it to overwrite template fields. The key part to this is that the triple square bracket appears to trigger a bug so it reads [[[1d2]]] as if it were [[ [[1d2]] ]]. Because it’s nested during the roll resolution that nested roll is replaced by plain numeric value but then there aren’t enough square brackets to find a new roll afterwards. So it becomes [1] or [2] but that is still valid for the link markdown of [1](#) or [2](#) The other form comes from trying to reuse a nested roll earlier than it appears $[[0]] [[ [[1d6]] + 3]] Because the d6 is nested it tries to replace it with the plain numeric but because of the early reference it replaces in the wrong place. During the resolution loop the macro text is altered during the course of the loop as follows $[[0]] [[ [[1d6]] + 3]] $[[0]] [[ $[[0]] + 3]] 2 [[ $[[0]] + 3]] Then the remaining $[[0]] is replaced by the HTML version and the outer roll never gets resolved Because we are getting the plain numeric result that result can be used in earlier rolls but at the cost of not using them where they are [[ $[[0]]+1]] [[ [[1d6]] ]] Nesting a roll can also be used to ‘unbox’ values &{template:default} [[ {{Encounter=There are [[2d2]] orcs in the road}} $[[]]] In this instance however the extra text would break the parser because of the text that cannot be inside a roll. However, ending the nesting with $[[]]] instead of ]] prevents that problem by mimicking an unresolved roll. The $[[]]] is optional if you want to affect all the remaining rolls but including it ends the effect allowing further rolls to work normally. It is worth noting that the plain numeric result of a text-based rollable table entry is zero and not the text and the result of a specific inline roll can only be made plain numeric once and thus only used in one inline roll
Dynamic roll indices One of the ways to use plain numeric results is to use a roll to determine which roll should be displayed, similar to roll index manipulation . This way is easier to manage but is limited to one result per choosing roll rather than allowing multiple. &{template:default} [[1d4+1]] [[1d6+2]] [[2d4+3]] {{Raw Roll=$[[3.computed]]}} {{Outcome=$[[$[[4]].computed]]}} [[ [[ [[1d3]]-1]] $[[]]] Note the use of .computed versions to prevent the Raw Roll getting the plain numeric result also whilst you can nest references without using .computed (eg $[[$[[4]]]]]) the run of closing square brackets triggers a bug where it would treat further rolls as not being nested (specifically one level of nesting down)
Improved roll index manipulation Using a dynamic roll index it becomes possible to perform roll index manipulation without relying on a differing amount of roll requests allowing it to scale more easily. The previous 3 output example could be written &{template:default} $[[$[[1]]]] [[[ [[{[[1d10cs>8cf<3 Raw roll]],6}>8f<3 +3]] ]]] [[ [[ [[0]] ]] ]] [[321 High outcome]] [[210 Mid outcome]] [[109 Low outcome]] {{Raw Roll=$[[0]]}} {{Outcomes=$[[5]]}} This method works by preventing inline rolls being assigned an index by short-circuiting the zero calculation tower. Note that since it uses the standard roll reference the next roll requires triple square brackets in order avoid a parser bug that would prevent the inner roll being treated as nested. Duplicating a roll result for further calculations Using everything thus far allows for roll index manipulation to duplicate a roll result and then perform additional calculations on that result. This example will roll a 1d3; uses that result to roll twice that many d4's and add the d3 to it again. &{template:default} [[[ (2*$[[4]])d4 + $[[7]] ]]] $[[$[[0]]]] [[[ [[1d3]] [[ [[ [[0]] ]] ]] [[3]][[2]][[1]][[3]][[2]][[1]] $[[10]] [[ [[ [[0]] ]] ]] ]]] {{Raw Roll=$[[0]]}} {{Double Usage Roll=$[[8]]}} Breaking down what the various parts do [[ (2*$[[4]])d4 + $[[7]] ]] This is the final calculation using roll 4 and 7 to reference the duplicated value via index manipulation. It will have an index of 8. If further roll duplication is required this will require triple brackets to not alter further index assignment. $[[$[[0]]]] [[[ The dynamic reference used to perform the index manipulation plus a triple bracket to treat further rolls as nested [[1d3]] The actual d3 with index 0 [[ [[ [[0]] ]] ]] The calculation tower that creates one, two or three indices [[3]][[2]][[1]][[3]][[2]][[1]] The duplicated roll results. These results don't have to be a perfect duplicate but can instead be a value derived from what would be the result. $[[10]] [[ [[ [[0]] ]] ]] Undoes the index manipulation to allow further indices to be consistent ]]] Finally a triple bracket to close the previous opening brackets For a real world example of this by TonidePony see their post for Choas Bolt & Wild Magic Surge including some additional comments by me of extensions and handling large values.
1644871259
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Nice write up Rainbow! I wasn't aware of most of these, but I've got one adjustment to note. For accessing the plain numeric result , you don't actually need the $[[]] at the end. Putting the excess starting inline roll bracket is enough, and putting it outside a roll template field hides it. Also, this trick will affect all rolls made after the excess brackets have been placed. So, this: &{template:default} [[ {{Encounter=There are [[2d2]] orcs in the road}} {{test=[[1d100]]}} Gives this:
You're right the $[[]]] isn't required if you want to affect all the rolls after the initial [[. The $[[]]] does however end the effect allowing further rolls to work normally. I've edited it to include this info.
Added an improved method for roll index manipulation and a method for roll result duplication to allow the value of a roll to be used multiple times.
Wow, so there is a way to duplicate roll result just to be able to use them multiple times. Great job finding this, now i just need to understand how to use it if i want to :)
1648700745

Edited 1648700851
Hello RainbowEncoder.  I've been trying to create a "simple" macro to make an attack and output damage, automatically including the critical damage if a crit is rolled.  Based on your and Nygaard's recent work, I've been able to come up with this to output an attack roll with damage, and it will modify the damage if it crits.    However, as you guys have shown, if I attempt to add any modifiers onto the attack reference, it breaks the whole thing.  I was reading through the techniques above and I was having trouble understanding them.  Would the work you have done here allow for the single d20 roll to be used with additional calculations?  And if so, could you help me with explaining how to do that and/or give an example of how that would apply in this situation?  I appreciate any help you can give in this regard.
The above techniques aren't necessary for what you want to achieve. As such I don't recommand them. Your macro can be simplified, including a dynamic modifier, as such &{template:default} {{name=Attack}} {{Attack=$[[2.computed]]}} {{Damage=[[ [[@{blade}+@{dex}]] + [[@{blade}]]*{1d0cs1+[[1d20+?{Mod|1}]]-(?{Mod})}>20]]}} Also in the future I recommand including your macro as text rather than an image as it's easier to check that way
Hi! Just like @Arbiter, I was curious on how to do a damage macro that would inclide crit damage in that case. I was wondering what the 1d0cs1 is used for here? It feels kinda useless...
Kevin J. said: Hi! Just like @Arbiter, I was curious on how to do a damage macro that would inclide crit damage in that case. I was wondering what the 1d0cs1 is used for here? It feels kinda useless... When using group rolls with target number and you only have a single expression in the group there has to be precisely one roll; Otherwise the parser complains. The 1d0cs1 adds nothing to the expression but exists to make the group roll with target number work. If you need specific helping creating a macro I recommend creating a new thread where more people will see it.
1651577945

Edited 1651578767
DrDubz
Marketplace Creator
Yo, I've been trying to work out a way to call a previous selection of dice rolls as apposed to their total for Lancer style crits (wherein you roll twice the number of dice and take the highest results of the base number of dice rolled, so like if damage is 2d6, a crit would be 4d6kh2) The problem I'm having is the sheets don't automatically do crits since there's no way to force a crit on a total attack roll of 20 or above (not nat 20) so I've had to make whole macros and completely ignore the sheet's weapon section. Recently however I'm having a play around and have found I can call the result by placing a little Crit = $[[1]] in the weapon tab note section. Question is, and a simple yes no answer is fine because I don't know if I should give up or not, is it possible to call the dice results, roll an extra set, then total a result based on keeping a selection of the highest results of those dice (like if the weapon damage is 3d6, can I call the results of those three d6, not the total, then roll another three and get a new total based on the highest three of the six dice rolls?) Hope I explained it correctly, and yeah, a yes no answer is fine, I just wanna know if I should stop beating my head against a wall.
No. When you reference an inline roll you can at best only get the total value out of it not any of the individual parts/rolls that make it up. So it cannot be used to do what your attempting. There may be other ways to retrofit the character sheet. To get specific help I recommend creating a new thread where people more familiar with the sheet will see it.
1651845623
DrDubz
Marketplace Creator
RainbowEncoder said: No. When you reference an inline roll you can at best only get the total value out of it not any of the individual parts/rolls that make it up. So it cannot be used to do what your attempting. There may be other ways to retrofit the character sheet. To get specific help I recommend creating a new thread where people more familiar with the sheet will see it. That's all I needed to hear honestly. Thankyou!