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 .
×
Cloudflare is experiencing elevated issues across all platforms. Load times for pages and images may be longer than expected. We are continuing to monitor the situation.

Order of Operations Issue. (Fetch, CharSetAttr, Muler, Zeroframe)

1680298833

Edited 1680298970
Hey, I'm a newbie at writing code in roll20 so please bear with me. The macro I wrote is below. The idea is that when it is run the user is given an input box to select one of items from a list of attributes. The list outputs the name of an attribute and then looks up the value of the attribute based on the name selected and stores both of these things in variables in an ability using Muler/set. I'm able to store the values of the selection just fine. I'm running into problems however when I try to output an inline roll to chat using the value I looked up (code A below) as no roll ever happens because of the Fetch call for some reason. So i thought maybe I could store this looked up value in a character attribute and then use the character attribute for the inline roll (code B below), a roll happens, but the parser is evaluating that attribute before anything else so the inline roll ends up using the attribute's previous value before the code above it runs. What is the best solution to this? code A: !{{?{Order|Time/Blood:@{Daenon|Time/Blood},Time/Blood|Space/Size:@{Daenon|Space/Size},Space/Size|Earth/Ground:@{Daenon|Earth/Ground},Earth/Ground|Air/Thought:@{Daenon|Air/Thought},Air/Thought|Water/Psyche:@{Daenon|Water/Psyche},Water/Psyche|Cold/Stars:@{Daenon|Cold/Stars},Cold/Stars|Fire/Heat:@{Daenon|Fire/Heat},Fire/Heat|Light/Image:@{Daenon|Light/Image},Light/Image|Life/Nature:@{Daenon|Life/Nature},Life/Nature|Dark/Fear:@{Daenon|Dark/Fear},Dark/Fear|Charm/Joy:@{Daenon|Charm/Joy},Charm/Joy} set.Daenon.vars.attuned = ?{Order}/set set.Daenon.vars.order = @(Daenon.?{Order})/set }} /em attunes to ?{Order} and attempts an Arcane Secret with [[(@(Daenon.?{Order})+?{Potential spent|1}+@{Daenon|Fx Truesight})d6>5]] successes! code B: !{{?{Order|Time/Blood:@{Daenon|Time/Blood},Time/Blood|Space/Size:@{Daenon|Space/Size},Space/Size|Earth/Ground:@{Daenon|Earth/Ground},Earth/Ground|Air/Thought:@{Daenon|Air/Thought},Air/Thought|Water/Psyche:@{Daenon|Water/Psyche},Water/Psyche|Cold/Stars:@{Daenon|Cold/Stars},Cold/Stars|Fire/Heat:@{Daenon|Fire/Heat},Fire/Heat|Light/Image:@{Daenon|Light/Image},Light/Image|Life/Nature:@{Daenon|Life/Nature},Life/Nature|Dark/Fear:@{Daenon|Dark/Fear},Dark/Fear|Charm/Joy:@{Daenon|Charm/Joy},Charm/Joy} set.Daenon.vars.attuned = ?{Order}/set set.Daenon.vars.order = @(Daenon.?{Order})/set !setattr --name Daenon --AttunedOrder|@(Daenon.?{Order}) }} /em attunes to ?{Order} and attempts an Arcane Secret with [[(@{Daenon|AttunedOrder}+?{Potential spent|1}+@{Daenon|Fx Truesight})d6>5]] successes!
1680367283
timmaugh
Forum Champion
API Scripter
Hey, Daenon... (This first bit is an explanation for what is happening and why; if you want to jump to the solution, look for the TL;DR heading, below.) When you want to use a retrieved thing in a roll and that thing is either 1) something that can't be retrieved via normal Roll20 syntax (like the top position of a token), or 2) is changing in the middle of the command and you need to wait for the new value, you have two options to delay your roll. Both of them are going to need a Fetch construction that is, itself, delayed until the value is available (for something that isn't changing but you can't get it via normal Roll20 syntax, like the top value of a token, you wouldn't have to delay it at all if it wasn't changing). The "why" of it comes into timing... Roll20 order of operations retrieves attributes, abilities, and macros (99 times each), then it handles roll queries (99 times), then inline rolls. All of that happens before the mod scripts (including the metascripts) get the message. That's why when you use the Roll20 syntax the attribute is retrieved before it is set... because it's just retrieved first, before the mod has a chance to set the value. Interestingly, pertaining to your second option (code B), you can use delayed Roll20 syntax that specifically references a character because on the backend, ZeroFrame is dispatching *new* messages at each cycle to reengage the Roll20 parsers. You could not use a delayed @{selected|...} formation because for mod-dispatched commands, there are no selected tokens (until SelectManager restores them, but that only happens after the Roll20 parsers... so these formations won't resolve). So when I get to the ways to defer the roll or the Fetch construction, below, just know you could do the same thing with an attribute reference like you were trying to do, provided that attribute reference came with a specific character name. However, your first attempt at the code is closer to what you want, so we'll use that. One more bit of "front matter" to understand before we get to the actual fix... Every message that goes to the script will pass through this pattern: 1) Roll20 parsers (attributes, abilities, macros, queries, rolls) 2) Metascripts (if you have ZeroFrame, this is a loop of new messages, each time un-defering the command line 1 time, until there is no more meta-work to do; roll20 parsers engage each time, except that queries are not functional at this point) 3) Standard Scripts This is important to remember for lines like this: set.Daenon.vars.order = @(Daenon.?{Order})/set This line will work because the query will resolve before the metascript loop, and during the metascript loop, Fetch will resolve before the Muler-Set statement (you can see the order if you run !0 from the chat). Now, you're also using the ZeroFrame batching construction:  !{{ ... }} So everything between there is going to act as a single message, FIRST, then each individual line will be dispatched as its OWN message. Each of those messages (first and all of the seconds) will have their own Roll20 parsing and metascript cycle before being released to the standard scripts. So with your top level message (Code A), the query resolves first, then the metascripts have at the message (Fetch finds fetch constructions, Muler finds the set statements, etc.). Then the command line is released to the standard scripts... The ZeroFrame batching construction catches the message at standard-script-speed. So *NOW* ZeroFrame sees that it has a multi-command message, and it looks to dispatch each line. What is left of the batched lines, by this point, is: ...the result of your query selection ...a blank line (the set statement is removed) ...a blank line (the second set statement is removed) The blank lines are filtered out (not sent). MEANWHILE... Once Roll20 parsers are done with the first command (everything between the double-braces), they move on to the next command (the one that begins /em). That's why the roll you're trying to have happen in that command is happening too soon. The fix is to have the metascripts take ownership of that line and defer the roll. So, we've finally passed through the "why is it happening" and into the realm of "what the hell we gonna do about it?" It truly is a magical place. TL;DR We have the batching construction take ownership of the /em line by moving that line within the double-braces. The fact that it does NOT begin with a bang (!) means that when ZeroFrame gets ready to dispatch it, it will automatically apply the same {&simple} operation that will stop the message from proceeding out of the metascript loop and on to the standard scripts; instead, it will send the message (in whatever state it has reached at the end of the metascript work) to the chat output. (We could also have the metascripts take ownership of it by leaving it out of the batched set of commands and manually changing it to start with a bang and include a {&simple}... but since you already have the batching operation going, let's just use that). Now, we know the inline roll formation, when detected by Roll20, will parse immediately. We can't have that happen before the Fetch construction is detected, which means it can't sit in the line, looking like a normal inline roll construction.... so let's defer it. Option 1 - Backslashes (and a quirk for inline rolls) ZeroFrame uses backslashes as the deferral character for breaking up syntax constructions. At the end of every loop, as long as SOME metascript made a change (or there was an inline roll detected), a backslash is removed, and the new command line is sent through the Roll20 parsers, again. When you have a backslash in the right position in a syntax construction, the parsers won't see it: @(character.attribute) ...will be detected as a Fetch construction, but... @\(character.attribute) ...will not. After this pass of the ZeroFrame cycle ends, ZeroFrame will remove the backslash, and the next time Fetch looks at the message, it will find something it needs to act on. Multiple backslashes, then, indicate that-many-cycles to defer the construction: @\\(character.attribute) In your case, you don't need to defer Fetch, you need to defer your inline roll, and you only need to defer it one time (until Fetch can resolve, and Fetch will resolve in the first cycle). For inline rolls, we use the backslash, but for the leading brackets, we have to also close them individually (it's a quirk of Roll20 parsing; out of my hands): [\][\] ... \]\] That roll would be deferred for one cycle. For timing consideration, doing things this way would fulfill the roll parsing during the metascript cycle of the original message, before each individual  command line was dispatched. For your situation, that will be fine. But sometimes you don't want things to resolve until each individual line is sent. For that, see Option 2, below. Right now, this is the Option 1 way of writing your command line: !{{?{Order|Time/Blood:@{Daenon|Time/Blood},Time/Blood|Space/Size:@{Daenon|Space/Size},Space/Size|Earth/Ground:@{Daenon|Earth/Ground},Earth/Ground|Air/Thought:@{Daenon|Air/Thought},Air/Thought|Water/Psyche:@{Daenon|Water/Psyche},Water/Psyche|Cold/Stars:@{Daenon|Cold/Stars},Cold/Stars|Fire/Heat:@{Daenon|Fire/Heat},Fire/Heat|Light/Image:@{Daenon|Light/Image},Light/Image|Life/Nature:@{Daenon|Life/Nature},Life/Nature|Dark/Fear:@{Daenon|Dark/Fear},Dark/Fear|Charm/Joy:@{Daenon|Charm/Joy},Charm/Joy}   set.Daenon.vars.attuned = ?{Order}/set   set.Daenon.vars.order = @(Daenon.?{Order})/set   /em attunes to ?{Order} and attempts an Arcane Secret with [\][\](@(Daenon.?{Order})+?{Potential spent|1}+@{Daenon|Fx Truesight})d6>5\]\] successes! }} Option 2 - Batch Deferral ZeroFrame batch lines can have individual characters declared as deferral characters. These characters aren't removed as part of the initial metascript loop... they are removed "just in time" as each sub-command line is dispatched individually. You can declare a batch-level deferral or a line-level deferral... the syntax is the same, it's just the placement that determines priority/meaning. To declare a deferral character/set-of-characters, enclose it in parentheses: (^) ...would use the caret character as a deferral character, which could then be used to break up constructions: @^(character.attribute) [^[^ ... ]^] (oddly, the above inline roll syntax does not trip the same Roll20 parsing quirk as the backslash did, where we would then have to "close" each opening bracket.) Batch-level deferral happens when you place the parentheses-enclosed characters right after the double-opening brace in your batch: !{{(^)   ... }} Batch-level deferrals will affect *every* sub command in the batch. Line-level deferrals happen when you place the parentheses-enclosed characters at the start of a sub-command line: !{{   (^)... }} Line-level deferrals will only affect the line they are found on. For your situation, you would only need the deferral in your /em line, so we can use a line-level deferral. That would look like this: !{{?{Order|Time/Blood:@{Daenon|Time/Blood},Time/Blood|Space/Size:@{Daenon|Space/Size},Space/Size|Earth/Ground:@{Daenon|Earth/Ground},Earth/Ground|Air/Thought:@{Daenon|Air/Thought},Air/Thought|Water/Psyche:@{Daenon|Water/Psyche},Water/Psyche|Cold/Stars:@{Daenon|Cold/Stars},Cold/Stars|Fire/Heat:@{Daenon|Fire/Heat},Fire/Heat|Light/Image:@{Daenon|Light/Image},Light/Image|Life/Nature:@{Daenon|Life/Nature},Life/Nature|Dark/Fear:@{Daenon|Dark/Fear},Dark/Fear|Charm/Joy:@{Daenon|Charm/Joy},Charm/Joy}   set.Daenon.vars.attuned = ?{Order}/set   set.Daenon.vars.order = @(Daenon.?{Order})/set   (^)/em attunes to ?{Order} and attempts an Arcane Secret with [^[^(@(Daenon.?{Order})+?{Potential spent|1}+@{Daenon|Fx Truesight})d6>5^]^] successes! }} Again, for timing purposes, this option would have the roll occur during the message created by dispatching the given sub-command. Either of these options should work for what you're trying to do. Give them a try and let me know if you have any other questions!
This is amazing. Thanks for the lengthy explanation. Could you help me understand one more thing about the deferrals why they are used after certain characters like the opening bracket [ and before other characters like the close bracket ]? I guess I was thinking about them like they kind of behaved like the syntax for writing comments in code // but it seems the parser is doing something else that I don't quite understand.
1680527400
timmaugh
Forum Champion
API Scripter
Sure... the "why" is a bit more like pattern matching... Roll20 looking for a pattern of double opening brackets, and then consuming everything up to double closing brackets as the contents of an inline roll. That means the leading characters (opening double brackets) are more important to break up than the trailing ones, and sometimes you can get away with not deferring the trailing ones, but I make it a habit anyway. For instance, if you have nested rolls, I want each to specifically manage what closure goes with what opening. I want them to come "available", or "detectable" at the same time. I don't want a closure inadvertently closing the incorrect roll, or otherwise tripping up the Roll20 parser.