Difference between revisions of "Script:ZeroFrame"
From Roll20 Wiki
(→The TEMPLATE Tag) |
(→Delaying a Message) |
||
(25 intermediate revisions by one user not shown) | |||
Line 62: | Line 62: | ||
The above will roll a number of d10 equal to the '''top''' property of the Bob token. Since inline rolls are resolved before the message is handed off to the installed mod scripts, and since we can't retrieve the '''top''' value of the token until the message is already with the mod scripts, we have to disguise the roll. On the first pass of the ZeroFrame loop, Fetch will retrieve the value. At the end of the first pass, one level of deferral characters is removed. This exposes the roll to be detected as the second pass of the ZeroFrame loop begins. | The above will roll a number of d10 equal to the '''top''' property of the Bob token. Since inline rolls are resolved before the message is handed off to the installed mod scripts, and since we can't retrieve the '''top''' value of the token until the message is already with the mod scripts, we have to disguise the roll. On the first pass of the ZeroFrame loop, Fetch will retrieve the value. At the end of the first pass, one level of deferral characters is removed. This exposes the roll to be detected as the second pass of the ZeroFrame loop begins. | ||
− | You only need one deferral bracket no matter how many loops you are deferring for (that is, only one closing bracket no matter the number of backslashes): <code>[\\\][\\\]@\\(Bob.top)d10\\\]\\\]</code>. | + | You only need one deferral bracket no matter how many loops you are deferring for (that is, only one closing bracket no matter the number of backslashes): |
+ | |||
+ | <code>[\\\][\\\]@\\(Bob.top)d10\\\]\\\]</code>. | ||
The example can get much more complicated as you use other Meta Toolbox scripts in the same formation: | The example can get much more complicated as you use other Meta Toolbox scripts in the same formation: | ||
Line 143: | Line 145: | ||
That command detects whether you have an NPC token selected (by testing the <code>npc_ac</code> Fetch construction with a default value of <code>none</code>), and outputs a different roll template depending on the result. | That command detects whether you have an NPC token selected (by testing the <code>npc_ac</code> Fetch construction with a default value of <code>none</code>), and outputs a different roll template depending on the result. | ||
− | |||
==Managing and Re-Using Rolls== | ==Managing and Re-Using Rolls== | ||
+ | Roll20 lets you re-reference rolls using roll markers (i.e., <code><nowiki>$[[0]]</nowiki></code>), but prior to ZeroFrame there was no real way to re-use rolls in a second/separate calculation (another roll). | ||
+ | |||
+ | ===Getting an Inline Roll's Value=== | ||
+ | You can append <code>.value</code> to any roll or roll marker to have ZeroFrame extract the roll's value right in the line. This is the equivalent of substituting the value of the roll into that position in the command line. | ||
+ | |||
+ | <pre>!somescript --attackval|[[1d20+2]].value --altattack|$[[0]].value | ||
+ | ...becomes... | ||
+ | !somescript --attackval|14 --altattack|14</pre> | ||
+ | |||
+ | ====Automatic Unpacking==== | ||
+ | Typically, you only need to do this in a situation where the roll will not be unpacked automatically. Automatic unpacking happens for nested inline rolls (see below), for rolls that are output to a simple/flat chat message (see below), as well as in certain meta-script constructions (in an APILogic IF condition, or a MathOps tag, for example). Also, it's good to remember that if you are sending a macro or command line to the API that is intended for another script, there is a good chance that other script knows what it wants to do with inline rolls, already, so if you don't need to actually use the value of the roll, you could just leave it in the command line. | ||
+ | |||
+ | ====Roll Availability==== | ||
+ | The Roll20 parsers detect inline rolls, process them, and leave behind roll markers in the command line (things like <code><nowiki>$[[0]]</nowiki></code>). This happens before each pass of the ZeroFrame loop. Also with each pass of the loop, ZeroFrame evaluates the <code>.value</code> token to look for places where you want to unpack a roll. If a roll is not available at the time that the <code>.value</code> token is detected, the token will be left in the command line (to allow for the referenced inline roll becoming available in a later loop pass) unless ZeroFrame detects that it is finished working with the roll. If ZeroFrame detects there is no more work to do, the unpacking of the <code>.value</code> token returns a 0. | ||
+ | |||
+ | As an example: | ||
+ | |||
+ | <pre> | ||
+ | !somescript --roll0|[[1d10]] --roll1|[\][\]1d20\]\] --arg|$[[1]].value --arg | ||
+ | </pre> | ||
+ | |||
+ | In the above command line, the rolls will be indexed as: | ||
+ | |||
+ | <pre> | ||
+ | $[[0]] => [[1d10]] | ||
+ | $[[1]] => [[1d20]] | ||
+ | </pre> | ||
+ | |||
+ | However, roll <code><nowiki>$[[1]]</nowiki></code> isn't available in the initial pass (it is deferred). In that case, knowing that there is another loop coming, ZeroFrame doesn't try to extract the value immediately. After the next loop, the roll ''is'' available, so ZeroFrame can extract the value, then. | ||
+ | |||
+ | Here is another example in a templated output with various usages. Note the way this can both re-use rolls (with hover tips) in a simple message, and how it can extract the values from rolls that aren't available until later passes of the loop. Finally, note that the <code><nowiki>$[[3]]</nowiki></code> roll does not exist in this command line, so when ZeroFrame goes to release the message, the <code><nowiki>$[[3]].value</nowiki></code> construction is replaced with a 0: | ||
+ | |||
+ | <pre>!&{template:default}{{name=Roll Proof}}{{Roll0=[[1d20]]}}{{Roll 1=[\][\]2d20\]\]}}{{Roll 2=[\\][\\]3d20\\]\\]}}{{Re-Use 0=$[[0]]}}{{Re-Use 1=$[[1]]}}{{Re-Use 2=$[[2]]}}{{Value 1=$[[1]].value}}{{Value 2=$[[2]].value}}{{Non-Existent 3=$[[3]].value}}{&simple}</pre> | ||
+ | |||
+ | [[File:ZeroFrameRollReUseDemo.png]] | ||
+ | |||
+ | ===Nesting Inline Rolls=== | ||
+ | As just demonstrated, ZeroFrame takes advantage of its loop to give you the ability to nest your reused inline rolls. We can take this a step further and re-use our rolls in other rolls. For instance, we know that the first inline roll detected by the Roll20 parser can be reused in the command line by using the <code><nowiki>$[[0]]</nowiki></code> marker. However, using that marker nested inside another inline roll would break the Roll20 parser and throw an error: | ||
+ | |||
+ | <pre>[[ $[[0]]d10 ]]</pre> | ||
+ | ''(this breaks in a normal chat call!)'' | ||
+ | |||
+ | We can make this sort of usage work with ZeroFrame with one minor change of escaping the outer roll as mentioned above: | ||
+ | |||
+ | <pre>[\][\] $[[0]]d10 \]\]</pre> | ||
+ | |||
+ | When ZeroFrame sees a nested roll such as this (one roll marker used in another roll), it will automatically extract the roll's value and insert it into the command line in place of the roll marker. You do '''not''' need to use a <code>.value</code> structure. | ||
+ | |||
+ | Since the outer roll is dependent on the inner roll's value, we must slow it down by one loop (one deferral). Specifically, we must slow it down so that it only becomes detectable as an inline roll one loop cycle after the nested roll is available. In the above case, we are assuming that the <code><nowiki>$[[0]]</nowiki></code> roll is available in the first loop pass, so we only need to slow down the outer roll by one pass to allow ZeroFrame the opportunity to extract the roll value. | ||
+ | |||
+ | Nest multiple levels of inner rolls by using 1-more escape character for each outer wrapping of inline roll structure, as mentioned above in the section [[Script:ZeroFrame#Escaping_and_Deferring_Text_.28Delayed_Processing.29|Escaping and Deferring Text (Delayed Processing)]]. | ||
+ | |||
+ | ==Delaying a Message== | ||
+ | Use the <code>{&delay}</code> tag to delay processing of a given message. Use a number to denote the number of seconds to delay: | ||
+ | |||
+ | <pre>!This message will delay for 2.4 seconds.{&simple}{&delay 2.4}</pre> | ||
+ | |||
+ | The delay tag is detected prior to all other tags (<code>{&stop}</code>, <code>{&simple}</code>, <code>{&global}</code>, <code>{&escape}</code>, etc.) except for <code>{&skip}</code>, meaning that the delay can give game elements time to change prior to retrieval for the command line. For instance, if a delayed line contained a Fetch reference to a character attribute, the attribute could change during the span of the delay prior to Fetch going out to retrieve the data. | ||
+ | |||
+ | Note that Roll20 constructions cannot be delayed, since these will occur prior to ZeroFrame getting a chance to act on the message. Inline rolls, queries, and attribute/ability/macro retrieval will all happen prior to the ability to delay the message. | ||
+ | |||
+ | For this reason, if you need to escape certain constructions until the message has been properly delayed, the delay tag also allows for the declaration of a deferral string. Simply include the characters to be used as the deferral within parentheses attached to the end of the word "delay" in the delay tag: <code>{&delay(^) 2.4}</code>. That will remove the '''^''' character anywhere within the command line at the point that the delay has finished running. Hide Roll20 constructions by interrupting the characters in the usage string, for instance: | ||
+ | |||
+ | <pre> | ||
+ | @^{Bob the Hirsute|ac} | ||
+ | </pre> | ||
+ | |||
+ | Roll20 references to the selected token will not work as a delayed retrieval: | ||
+ | |||
+ | <pre> | ||
+ | @^{selected|ac} | ||
+ | </pre> | ||
+ | |||
+ | ...since the delayed message will not have selected tokens. For this reason, use Fetch. | ||
+ | Batched lines (see Batching Sub-Commands, below), that are individually delayed will delay all subsequent commands. | ||
==Batching Sub-Commands (Sharing Rolls Across Messages)== | ==Batching Sub-Commands (Sharing Rolls Across Messages)== | ||
− | ==The | + | Roll20 gives you the opportunity to create "macros" of commands. These can either be stored as Collections Tab Macros or as Ability Macros (on a character sheet). However, sometime in the recent past the community started reporting that commands would be randomly dropped from multi-command messages, making the basic functionality unreliable if not unusable. With ZeroFrame's batching ability, it restores this basic Roll20 multi-line functionality, and it delivers certain new benefits. |
+ | |||
+ | ===Basic Syntax=== | ||
+ | If you have a series of commands you have to execute together, simply wrap your existing macro like this: | ||
+ | |||
+ | <pre>!{{ | ||
+ | ...commands here... | ||
+ | }}</pre> | ||
+ | |||
+ | For instance, your finished macro might look like this: | ||
+ | |||
+ | <pre>!{{ | ||
+ | !token-mod --set bar1|-1 | ||
+ | !Spawn --name|Giant Ape --offset|1,0 | ||
+ | }}</pre> | ||
+ | |||
+ | Any command that begins with a bang (e.g., <code>!</code>) will be dispatched as a mod script message intended for the Script Moderator. If a command does '''not''' start with a bang, ZeroFrame will assume you want that particular line sent as a chat-output message (not intended for the Script Moderator), however it structure the outbound line by wrapping it in a bang and a <code>{&simple}</code>: | ||
+ | |||
+ | <pre> | ||
+ | !{{ | ||
+ | This will hit chat. | ||
+ | }} | ||
+ | </pre> | ||
+ | |||
+ | In that example, the line <code>This will hit chat.</code> will actually be dispatched as: | ||
+ | |||
+ | <pre>!This will hit chat.{&simple}</pre> | ||
+ | |||
+ | This gives you the opportunity for a ''separate'' ZeroFrame loop when the new message is caught. | ||
+ | |||
+ | ===Deferral Characters=== | ||
+ | Batched commands give you two more ways to defer constructions, both of them related to making a construction available only when an individual sub-command is dispatched as its own message. | ||
+ | |||
+ | ====Defer All==== | ||
+ | To declare a string of characters as your deferral string and have it apply to ''every'' command in the set of batched commands, enclose it in parentheses immediately following the opening double-brace: | ||
+ | |||
+ | <pre>!{{(^) | ||
+ | ... | ||
+ | ... | ||
+ | }}</pre> | ||
+ | |||
+ | That will remove the <code>^</code> character from every command line prior to sending. | ||
+ | |||
+ | ====Defer For Single Line==== | ||
+ | To use a deferral string for only a single command line, enclose it in parentheses and include it at the start of the line (prior to any characters other than spaces): | ||
+ | |||
+ | <pre>!{{ | ||
+ | (^)... | ||
+ | ... | ||
+ | }}</pre> | ||
+ | That would, for only the one line, remove any <code>^</code> characters found. | ||
+ | |||
+ | ====Considerations==== | ||
+ | You can use both kinds of escape characters (escaping for all lines and separately for a given line) together. Both escaping replacements will be applied (the escape-all deferral will happen, followed by any local deferral for a given line). | ||
+ | |||
+ | Deferral strings (in either location) can be more than a single character: | ||
+ | |||
+ | <pre>!{{(defer) | ||
+ | !script --@defer(selected.attribute) | ||
+ | }}</pre> | ||
+ | |||
+ | ====Example 1==== | ||
+ | This uses deferrals to make sure the roll in the second command isn't evaluated until the line is dispatched: | ||
+ | |||
+ | <pre>!{{ | ||
+ | !scriptOne --args | ||
+ | (^)!scriptTwo --roll [^[^2d20^]^] | ||
+ | }}</pre> | ||
+ | |||
+ | ====Example 2==== | ||
+ | Sabor has the ability to transfer health from one familiar to the other. The names of each familiar are stored in attributes on Sabor's character sheet (FamiliarOne, FamiliarTwo). This is the macro that would send the health to his first familiar. Each command in this set requires a different token to be selected when the command is evaluated (using the <code>{&select ...}</code> syntax of [[Script:SelectManager|SelectManager]]). | ||
+ | |||
+ | <pre>!{{(^) | ||
+ | !script --health|+1 {^&select @(@{Sabor|FamiliarOne}.token_id)} | ||
+ | !script --health|-1 {^&select @(@{Sabor|FamiliarTwo}.token_id)} | ||
+ | }}</pre> | ||
+ | ''(Requires [[Script:SelectManager|SelectManager]] and [[Script:Fetch|Fetch]])'' | ||
+ | |||
+ | ===Handling Double-Braces in Sub-Commands=== | ||
+ | Do to a quirk of Roll20 parsing that happens before ZeroFrame could even see the message, double-braces in sub-commands had been initially problematic for ZeroFrame batching operations (the message would be broken apart at the Roll20 level). As of v1.1.1, ZeroFrame now handles double-braces in sub-commands by giving you a special replacement character string. | ||
+ | |||
+ | If you have a sub-command that would include double-braces (either a template command or a mod script command that utilizes template syntax to, itself, break across multiple lines), use this replacement pattern: | ||
+ | |||
+ | <pre> | ||
+ | {{ => ({) | ||
+ | }} => (}) | ||
+ | </pre> | ||
+ | |||
+ | For example, here is how to embed a multi-line script command line within a batch: | ||
+ | |||
+ | <pre>!{{ | ||
+ | !script ({) | ||
+ | --arg1|foo | ||
+ | --arg2|bar | ||
+ | (}) | ||
+ | ... | ||
+ | ... | ||
+ | }}</pre> | ||
+ | |||
+ | If one of your sub-commands uses a roll template, you should defer the <code>&{template:...}</code> formation using some form of line deferral so that the template declaration does not get recognized and applied to the overall message (this would cause unexpected results). Deferral might look like this: | ||
+ | |||
+ | <pre>!{{ | ||
+ | (^)&^{template:default} ({)name=Deferred Template(})({) ... (}) | ||
+ | ... | ||
+ | }}<pre> | ||
+ | |||
+ | Similarly, if you use a ZeroFrame TEMPLATE tag intended for a sub-command, you should defer it, as well: <code>{^&template:...}</code> | ||
+ | |||
+ | <pre> | ||
+ | !{{ | ||
+ | {&if @{selected|bar1} >= 0} | ||
+ | (^){^&template:default}({)name=Deferred Template(})({) ... (}) | ||
+ | {&else} | ||
+ | (^){^&template:npcaction}({) ... (})({) ... (}) | ||
+ | {&end} | ||
+ | ... | ||
+ | ... | ||
+ | }}</pre> | ||
+ | |||
+ | That example tests the value of bar1 for a selected token, then it runs one template for a true result and another for a false result. Both templates are deferred, which means -- so far -- that the deferral character could have been declared as a batch-level deferral, immediately following the opening double-brace, like this: <code>!{{(^)</code>. If that character becomes necessary to another line in the batch (what is elided in the ellipsis), then a different character series could be used or the necessary lines which require construction deferral could have their own characters declared (as above). | ||
+ | |||
+ | ===Re-Use Rolls Across Command Lines=== | ||
+ | One of the benefits of batching multiple command lines is the ability to use rolls in different command lines. Since your original message hits the parser as a single command line (before ZeroFrame separates individual commands), ''all'' of the rolls present in any of your individual commands are evaluated at once. ZeroFrame continues to make them available to all of the individually-dispatched messages. | ||
+ | |||
+ | <pre>!{{ | ||
+ | Spanklefist attacks Bobbidangler and rolls [[1d20]]. | ||
+ | {&if $[[0]] > 10} ... | ||
+ | }}</pre> | ||
+ | |||
+ | In that example, the roll is first made as a statement that will be sent to chat announcing the attempted attack, then the roll is re-used in the conditional block on a completely separate line. | ||
+ | |||
+ | ====One Caveat to Sharing Rolls==== | ||
+ | Since all of your individual sub-command lines within a batch are initially treated as a single message, all of those lines are now referring to the same pool of shared inline rolls. So if you were converting a macro that initially started like this: | ||
+ | |||
+ | <pre> | ||
+ | !script1 --arg1 [[1d20]] | ||
+ | !script2 --arg1 [[[[1d10]]d4]] --arg2 $[[0]] | ||
+ | </pre> | ||
+ | |||
+ | ...you should be aware that the <code><nowiki>$[[0]]</nowiki></code> of the second line would have referred only to its ''own'' command line previously, but in a batch it will refer to the overall pool of rolls. In this batch, the 0th roll will actually be the roll in the first sub-command line (associated with <code>script1</code>). | ||
+ | |||
+ | You can fix this by incrementing roll indices when you put them into batch: | ||
+ | <pre> | ||
+ | !{{ | ||
+ | !script1 --arg1 [[1d20]] | ||
+ | !script2 --arg1 [[[[1d10]]d4]] --arg2 $[[1]] | ||
+ | }} | ||
+ | </pre> | ||
+ | |||
+ | ===Re-Use All Meta Process Constructions (Definitions, Logic, Mules, etc.)=== | ||
+ | In a similar vein, since the entire batch of multiple sub-commands is initially treated as a single message, it will pass through a ZeroFrame loop. This means that just as you can re-use rolls across multiple command lines you can use meta-constructions in the same way. | ||
+ | |||
+ | This goes for APILogic definitions, conditionals, Muler mules, SelectManager select statements, and any other meta-operation that might be later added to the metascript toolbox. | ||
+ | |||
+ | ==The GLOBAL Tag (Declaring Variables)== | ||
+ | ZeroFrame allows you to declare "global" variables which are then used in text replacement operations throughout the command line at the start of every loop cycle. Global variables are declared using the GLOBAL tag: | ||
+ | |||
+ | <code>{&global ... }</code> | ||
+ | |||
+ | Within that construction, you can include as many term definitions as you need. A term definition looks like this: | ||
+ | |||
+ | <code>([term] definition)</code> | ||
+ | |||
+ | Therefore a global variable defining <code>theColor</code> to be <code>red</code> would look like this: | ||
+ | |||
+ | <code>{&global ([theColor] red)}</code> | ||
+ | |||
+ | Here is an example with two definitions in the same tag: | ||
+ | |||
+ | <code>{&global ([theColor] red) ([theRoll] [[2d20kh1]])}</code> | ||
+ | |||
+ | ====Use Unique Variable Names==== | ||
+ | Take care that your variables (your terms) are unique enough that they will not catch occurrences you do not intend them to. For instance, if you were defining a series of rolls, a construction pattern of <code>Roll1</code>, <code>Roll2</code>, <code>Roll3</code>, etc., might initially work. However, once you got up to defining <code>Roll10</code>, you would encounter the problem that <code>Roll1</code> searches would find <code>Roll10</code> usages. ZeroFrame would replace the "Roll1" portion of "Roll10", leaving the definition in place, followed by the trailing "0". | ||
+ | |||
+ | ===Multiple GLOBAL Tags and Overwriting Variables=== | ||
+ | You can have as many GLOBAL tags in your command line as you see fit, and as many definitions per tag as you require. You can also utilize the tag in any loop cycle (either by deferring it to later detection or by porting it in via some retrieval operation). | ||
+ | |||
+ | All global definitions are stored in the same pool, so detected definitions are added as they are found, then they are used against the command line in a series of replacements. If you redefine the same term in a subsequent definition or GLOBAL tag, the value is altered for future cycles of the loop. | ||
+ | |||
+ | ===Loop Considerations=== | ||
+ | As mentioned, you can include a GLOBAL tag in any loop, and the detected definitions will apply for all future loop cycles. This means that if you define a global variable in the intial loop, then retrieve a segment of the command line in the fifth pass of the loop, ZeroFrame will still look for the term you defined and replace it with your supplied definition. | ||
+ | |||
+ | Global variables defined in later cycles can help you properly refer to rolls, since by the time your command line enters more and more cycles, you might have introduced multiple new rolls. Or you might have introduced a different number of rolls from a previous usage of the same command line. How do you know whether you're working with the 11th roll or the 4th? You can define a unique term to refer to the roll: | ||
+ | |||
+ | <code>{&global ([metamorphRoll] [[1d20 - @{Morpho|metamorph_skill}]])}</code> | ||
+ | |||
+ | Global variables are detected at the start of each loop, prior to the installed metascripts. This is important to remember if you bring in a GLOBAL tag during a particular cycle: ZeroFrame will not recognize the definitions and will not search/replace them in the command line until the start of the ''subsequent'' loop. For example, consider this command line snippet: | ||
+ | |||
+ | <code>{&global ([theRoll] [[1d20]])} {&if theRoll > 10} ... {&else} ... {&end}</code> | ||
+ | |||
+ | That defines a global variable (<code>theRoll</code>), then uses the result of that roll to test in a conditional. If this snippet were a part of the command line from the initial loop, everything would function as you expect: the global variable is caught at the beginning of the loop and ZeroFrame performs the text-replacement prior to APILogic having a chance to evaluate the conditional. | ||
+ | |||
+ | On the other hand, if you used something like Fetch to return this snippet to your working command line during a subsequent loop, things will not work as written. Fetch would run before APILogic, but ''after'' the opportunity to detect the GLOBAL tag. So while Fetch would return the snippet with the GLOBAL tag present in it, the definition the GLOBAL tag contains would not be detected before APILogic. APILogic still has a chance to examine the command line during ''this'' cycle, before the cycle begins again and GLOBAL tags are detected again. In a case like this, you could defer the APILogic constructions with ZeroFrame loop deferrals (backslashes): | ||
+ | |||
+ | <code>{&global ([theRoll] [[1d20]])} {\&if theRoll > 10} ... {\&else} ... {\&end}</code> | ||
+ | |||
+ | ''Note: you have to defer all of the APILogic conditional constructors in the set.'' | ||
==Logging and Troubleshooting== | ==Logging and Troubleshooting== | ||
+ | If you need to troubleshoot a line to determine why it isn't evaluating or behaving the way you think it should, or if you just want to see how a command line changes throughout the loop process, include the <code>{&log}</code> token in your command line: | ||
+ | |||
+ | <pre>!somescript --arg1|[[1d10]].value {& log}</pre> | ||
+ | |||
+ | With that tag included, ZeroFrame will track how the command line changes as it is handed off to each script and across loops. At the end, it will show you an output that lists those changes so you can see how pieces are resolved. | ||
+ | |||
+ | [[File:ZeroFrameLogging.png]] | ||
+ | |||
+ | Each dot represents something different. | ||
+ | |||
+ | <pre> | ||
+ | Orange => Loop Iteration | ||
+ | Blue => Nothing detected/changed during this loop | ||
+ | Green => Something detected, successfully changed | ||
+ | Red => Something detected, could not resolve during this pass of the loop | ||
+ | </pre> | ||
+ | |||
+ | ===Unresolved Syntax and Stopping Endless Loops=== | ||
+ | If a metascript can't resolve a token for which it is responsible, it will raise an 'unresolved' flag and leave the structure in place (for the most part). The loop will operate again to see if another metascript might alter the command line in such a way that the token can be resolved. Because of this, we run the risk of infinite loops. | ||
+ | |||
+ | Certain Mule variable retrieval patterns can also cause infinite loops. If the result of a variable retrieval is, itself, a get.variable statement, which resolves into another get.variable, etc., the process runs the risk of never ending. Similarly, expanding inline rolls against tables which include the syntax to trigger another inline roll against the table, could create a series of inline roll resolutions that would never end. | ||
+ | |||
+ | ZeroFrame stops this by comparing the command lines at each pass of the loop to those that have come before to find duplicates. After a certain number of these identical command lines are detected, ZeroFrame ends it processing (leaving some syntax structures in place), and provides you the log screen so you can see what happened. | ||
<noinclude> | <noinclude> | ||
Latest revision as of 03:33, 9 May 2023
Page Updated: 2023-05-09 |
API:Meta-Toolbox
APIs to patch other API commands
Meta-Toolbox Scripts
- LibInline
- ZeroFrame
- SelectManager
- Fetch
- Muler
- MathOps
- Plugger
- APILogic
- Use Examples
created by Timmaugh
Related APIs
ZeroFrame
ZeroFrame(Forum) provides a way to organize, order, and loop over the other meta-scripts in the Meta-Toolbox. It can unpack inline rolls right in the command line, and lets you defer things like inline roll detection or the syntax token (the structures that would trigger the other meta-scripts) by escaping the text with a backslash(/
).
- ZeroFrame - Thread 1(Forum)
- ZeroFrame - Thread 2(Forum)
- ZeroFrame - Batching Operations(Forum)
Syntax:
!0 config => displays the ZeroFrame configuration (showing script priorities and handles) !0 sm|20 => sets the priority of SelectManager to 20 !0 logic|75 set|80 => multiple meta-scripts can be set in one statement (APILogic to 75, Muler Setting to 80) {& 0 get sm } => an inline tag for calls to another script changing the default loop order for this call (only), the order will be Muler Get, SelectManager, then the other scripts in default order .value => inline token to reduce an inline roll to its value (i.e., [[1d10]].value or $[[0]].value ) {& log} => inline tag to output the ZeroFrame log {& flat} => send the resulting message to the chat (no further API interaction), only detected after the loop finishes {& stop} => stops further processing (no chat, no API); only detected after the loop finishes {& escape ...} => characters to be removed only as the message leaves the metascript loop {& global ([term]def)} => term definition for text replacement (every loop pass)
[edit] Understanding MetaScripts and the ZeroFrame Loop
MetaScripts are scripts that operate on the message object prior to the message reaching the intended destination script. For instance, if you wanted to reference the top value of a token within the text of a command line intended for another script, Roll20 does not offer a built-in syntax structure to retrieve that value. That is, there is no @{selected|top} construction you could use in your command line to reference that value. Fetch, a metascript, does offer a way to get the top value of a token: @(selected.top)
, and since it is a metascript, it will return that information to the command line of the intended recipient script before that script examines the command line to determine what it needs to do.
MetaScripts, for all that they register to see the message before standard scripts, would still operate in a linear fashion in the order you install them. So if you installed Fetch before Muler, you could use Fetch to help determine which muled-variable you needed, but you couldn't use a muled-variable to help you determine which property you should fetch. This is where the ZeroFrame loop helps. ZeroFrame organizes all of the metascripts into a particular order and then repeatedly loops through them until there is no more meta-work to do: so long as one of the installed metascripts makes a change to the command line (or an inline roll is detected), ZeroFrame will run another pass through the loop.
[edit] The Loop Order
The default loop order is established to account for the vast majority of use-cases (though it can be changed). You can see your current order by running !0
from the chat input. This will show all of the installed metascripts along with their priority. Lower numbers will have a greater priority (e.g., they will run first) in the loop.
[edit] Setting Default Order (Permanent)
To change the order (permanently, so that all future calls will follow a new order), use an explicit call to ZeroFrame, setting the value for the priority of any script that requires it:
!0 sm|23 set|60
You can refer to the script by any of the shorthand references listed under the script name in the above config panel. For instance, you can affect APILogic by referring to apilogic, apil, or logic. Separate scripts by spaces, and separate the script from its intended priority with a pipe. The above example would give SelectManager a priority of 23, and the get portion of Muler a priority of 60.
[edit] Setting Default Order (Temporary)
If you have a default order of the metascripts that answers most of your needs, you may still encounter a case where you have to operate under a different order for a given call. For those specific calls, ZeroFrame also gives you an inline tag to order the scripts. The tag will only affect the command line into which they are placed, and will not affect your default order:
{& 0 get sm}
The above would load the get portion of Muler before SelectManager handled restoring selected tokens. The scripts that are included in the tag get hauled to the front of the loop order. Those scripts not mentioned are then added to the loop in their default order as it stands when the call is made.
DEFAULT ORDER : sm get fetch math eval set apil INLINE TAG : {& math get sm } ACTUAL TEMPORARY ORDER : math get sm fetch eval set apil
[edit] Setting Temporary Order and the Loop
ZeroFrame will look for the ordering tag at every pass of the loop, so if necessary you can fine-tune the loop order from one pass over the loop to the next. Note, if you do not change the loop order, it will remain whatever it was last set to (default or temporary).
[edit] Escaping and Deferring Text (Delayed Processing)
You can use escape characters to break up text formations (such as Roll20 structures or metascript syntax) so that they do not process before you are ready. ZeroFrame offers two ways to do this: one as each loop passes, and one as the message is exiting the loop and proceeding on to its destination. Provided you build your command line properly, the escaping characters are gone before the message is released to other scripts.
[edit] Deferring Text (Loop Deferral)
To defer text during a loop pass, use the backslash ( \
) character to escape the text. For instance, this backslash breaks up a Fetch construction: @\(Bob.top)
. One level of deferral is removed at the end of each pass of the ZeroFrame loop, so to defer a syntax structure for two loops, use two backslashes: @\\(Bob.top)
. Deferrals "hide" the syntax construction from being detected by the parsers (either Roll20 or metascripts), so as they are removed and the message is re-processed during the next loop, those constructions might become detectable again. This gives you a measure of control over when certain constructions are processed.
For inline rolls, use a single closing bracket after the escaping slashes for each opening bracket, and a simple backslash to escape closing brackets: [\][\]@(Bob.top)d10 \]\]
.
The above will roll a number of d10 equal to the top property of the Bob token. Since inline rolls are resolved before the message is handed off to the installed mod scripts, and since we can't retrieve the top value of the token until the message is already with the mod scripts, we have to disguise the roll. On the first pass of the ZeroFrame loop, Fetch will retrieve the value. At the end of the first pass, one level of deferral characters is removed. This exposes the roll to be detected as the second pass of the ZeroFrame loop begins.
You only need one deferral bracket no matter how many loops you are deferring for (that is, only one closing bracket no matter the number of backslashes):
[\\\][\\\]@\\(Bob.top)d10\\\]\\\]
.
The example can get much more complicated as you use other Meta Toolbox scripts in the same formation:
[\][\] {& math get.Bob.AttackMods.1d10.value * 2}d10 \]\]
There, the 1d10 is initially rolled, then the value is compared to a Mule called AttackMods (see the Muler wiki entry), then passed to a math operation where that is multiplied by 2 (see the MathOps wiki entry), before finally being used as the number of d10 to roll in the second pass. Since muler-get operations occur in the loop before MathOps operations, the math construction does not need to be deferred. At the end of the first pass, the math has reduced down to a number, and we are left with valid roll syntax. As that loop finishes, ZeroFrame removes one level of deferral, and the inline roll becomes exposed to the Roll20 parsers.
Remember, one level of escaping is removed at the end of each pass of the loop, so something requiring deferral until the second pass of the loop would have two escape characters. This is how you would control the default order of the metascripts for a particular pass (and subsequent passes). This example shows how to set the default order for the third pass of the loop: {\\& 0 get sm}
Note that there must be at least one indication that the loop should continue in order for a further loop pass to be initiated and a further level of escape characters to be removed. This is generally accomplished by having some modification to the command line (via one of the registered meta-scripts), or by the detection of an inline roll after the escape characters have been removed at the end of the loop pass. (The loop runs until it finds, in a single pass, no changes from any of the registered scripts and no new inline rolls detected after un-escaping the line.)
[edit] Escaping Text (After the Loop)
At the end of any loop where ZeroFrame did not detect that a metascript took action on the command line nor is there an inline roll waiting to be rolled, ZeroFrame will release the message, allowing it to pass on to the stack of standard mod scripts you have installed (see Using MetaScripts in Non-Script Commands (the SIMPLE Tag), below). At this point, ZeroFrame checks for an {&escape ... }
tag. Use this tag with a string of characters to designate that string for removal from the message prior to ZeroFrame releasing the message:
{& escape =} {& escape =+= } {& escape ~}
Because this happens after the loop, this can ensure certain constructions will remain in the command line. This can be helpful if you want to wait for further user interaction (like clicking a button) before retrieving certain data or having Roll 20 roll dice, or to establish a point where Roll20 will properly respond to queries and targeting statements (which require a user-generated message).
... @~{target|Target1|token_id} ... {&escape ~} ...
The above uses the tilde (a user-chosen character) to break up the targeting statement to keep it from being detected by Roll20. Then it uses the {&escape...} syntax token to remove that tilde as the message is being released. If the above is part of creating a chat button which should ask for a target, this ensures that the targeting statement survives until the user can click the button.
[edit] Advanced Usage
If you are spawning a command line which, itself, will be an mod script command line that requires further escaping (for another command line), you can escape the embedded escape token by using the string chosen in the first escape token:
... {==&escape ~} ... @~{target|token_id} ... {& escape ==} ...
In the above, nothing happens to the targeting statement in the top level macro. However, the escape token removes all ==
from the surviving message before it is released. This exposes a new escape token to the resulting (subsequent) command line. If that is a part of a mod script command, the message will be processed by the metascripts, and the new escape sequence (a tilde) will be removed from the line before the message is released from that ZeroFrame loop. This finally exposes the targeting statement, letting it survive until the proper (third) command sequence has reached the chat.
[edit] Using MetaScripts in Non-Script Commands (the SIMPLE Tag)
Use the {&simple}
or {&flat}
tag to output the command line, in whatever form results after metascript processing, to the chat window. The two versions, {&simple}
and {&flat}
are equivalent. If ZeroFrame sees one of these tags at the end of the final pass through the loop, it will stop the message from continuing on to your installed mod scripts and will instead redirect it to chat. That means that you could use one of these tags as a way to test various metascript formations to make sure you are getting back the information you need (or that your command line is in the form you are aiming to create). For example, checking that you are getting a Fetch construction returned to you:
!The value for the top of the token is @(selected.top){&simple}
[edit] The Two Kinds of Messages
Remember, metascripts are still mod scripts; they simply operate during the window of time between Roll20 parsing (getting attributes, abilities, macros, inline rolls, and queries) and standard mod scripts. The ability to produce a "simple" (e.g., chat-output) message might make it seem like metascript syntax can be used in normal, chat-bound messages -- and they can: those messages just have to start out as intended for the script moderator (starting with a bang: !
). It helps to understand that there are two kinds of general messages: those intended for the chat window, directly, and those intended to be passed to the script stack (or what we call the script moderator).
- Messages that start with a bang do not automatically hit the chat output. Anything that gets output to the chat panel as a result of running one of these lines is the product of a mod script generating the content.
- Messages that do not start with a bang are sent to the chat output after Roll20 parsers examine the line.
While mod scripts can listen for either/both kind of message, most mod scripts require the message to be specifically intended for that script. This is because nothing can stop a standard/simple chat message from hitting the chat output even if a mod script wants to take an action based on something it detected in the command line. ChatSetAttr is a good example of a script that works around this limitation, in that it lets you embed command lines between roll template parts in a standard message. The template still hits the chat window, and since templated messages do not display any of the text that is between the template parts, the ChatSetAttr command is never shown in the message output. Meanwhile, ChatSetAttr works in the background to take additional actions as included in the command line.
In the case of metascripts, the whole idea is that they will alter the message in some way as it passes through them. This ability is fairly well obviated if the original message hits the chat window prior to (and regardless of) anything the metascripts would try to do to it. This is why we say the ZeroFrame {&simple}
tag lets you use metascript constructions in a standard, chat-bound message, but in actuality what that requires is that the message start out as a bangsy message, and then include the {&simple}
tag. To put a fine point on it, these constructions will NOT work:
The top of the token is at @(selected.top). The top of the token is at @(selected.top).{&simple}
To make it work, start with a bang and include a {&simple}
somewhere in the command line that survives to the end of the last ZeroFrame loop:
!The top of the token is at @(selected.top).{&simple}
[edit] Using SIMPLE with a Roll Template
Following the above guidelines for starting messages with a bang to get the metascripts involved, you can use the {&simple}
tag to output a roll template message to chat. This way you could utilize all of the Meta Toolbox functionality as needed to arrive at the template syntax you need, then output the message when the meta-work has finished:
!/w gm &{template:default}{{name=Token Info}}{{Top=@(selected.top)}}{{Left=@(selected.left)}}{{Height=@(selected.height)}}{{Width=@(selected.width)}}{&simple}
[edit] Stopping or Skipping Processing
Once a metascript (like Fetch, APILogic, etc.) is installed, that script will examine every message to see if there is something for it to do. If, for any reason, you need to bypass metascript processing for a particular message, include a ZeroFrame {&skip}
tag in the command line. This will tell ZeroFrame not to let any metascript examine the command line, and to simply release the message as-is.
On a related note, if a message is passed through metascript examination during the ZeroFrame loop, but the result of that processing should result in no message (no message to the chat and no message continuing on to your standard scripts), you can use a ZeroFrame {&stop}
tag. This is particular useful with APILogic conditional constructions:
!{&if @(selected.top) > 700}token-mod --set top|-1u{&else} {&stop} {&end}
That command will move a selected token up one grid unit only if the starting value of the token's top property is more than 700. Otherwise, the message is stopped.
[edit] The TEMPLATE Tag
If you include a Roll20 &{template:...}
tag, the Roll20 parsers immediately consume that syntax and attach a template property to the message. However, using metascripts, we might want to decide on a template message later in the process (sometime during the loop), or we might be deciding between 2 or 3 potential templates depending on information the metascripts retrieve from the game. In this case, ZeroFrame offers a slight alteration on the Roll20 syntax structure: {&template:...}
.
Use this anywhere in the command line to have ZeroFrame detect the template you wish to use for the message. The detection of this tag comes at the end of the last pass through the loop, just before the message is released, and the last TEMPLATE tag detected will apply. Again, this is quite helpful with APILogic conditionals:
!{&if @(selected|npc_ac[none]) = none){&template:simple}{{rname=PC Action}}...{&else}{&template:npcaction}{{name=@{selected|token_name}}}...{&end}{&simple}
That command detects whether you have an NPC token selected (by testing the npc_ac
Fetch construction with a default value of none
), and outputs a different roll template depending on the result.
[edit] Managing and Re-Using Rolls
Roll20 lets you re-reference rolls using roll markers (i.e., $[[0]]
), but prior to ZeroFrame there was no real way to re-use rolls in a second/separate calculation (another roll).
[edit] Getting an Inline Roll's Value
You can append .value
to any roll or roll marker to have ZeroFrame extract the roll's value right in the line. This is the equivalent of substituting the value of the roll into that position in the command line.
!somescript --attackval|[[1d20+2]].value --altattack|$[[0]].value ...becomes... !somescript --attackval|14 --altattack|14
[edit] Automatic Unpacking
Typically, you only need to do this in a situation where the roll will not be unpacked automatically. Automatic unpacking happens for nested inline rolls (see below), for rolls that are output to a simple/flat chat message (see below), as well as in certain meta-script constructions (in an APILogic IF condition, or a MathOps tag, for example). Also, it's good to remember that if you are sending a macro or command line to the API that is intended for another script, there is a good chance that other script knows what it wants to do with inline rolls, already, so if you don't need to actually use the value of the roll, you could just leave it in the command line.
[edit] Roll Availability
The Roll20 parsers detect inline rolls, process them, and leave behind roll markers in the command line (things like $[[0]]
). This happens before each pass of the ZeroFrame loop. Also with each pass of the loop, ZeroFrame evaluates the .value
token to look for places where you want to unpack a roll. If a roll is not available at the time that the .value
token is detected, the token will be left in the command line (to allow for the referenced inline roll becoming available in a later loop pass) unless ZeroFrame detects that it is finished working with the roll. If ZeroFrame detects there is no more work to do, the unpacking of the .value
token returns a 0.
As an example:
!somescript --roll0|[[1d10]] --roll1|[\][\]1d20\]\] --arg|$[[1]].value --arg
In the above command line, the rolls will be indexed as:
$[[0]] => [[1d10]] $[[1]] => [[1d20]]
However, roll $[[1]]
isn't available in the initial pass (it is deferred). In that case, knowing that there is another loop coming, ZeroFrame doesn't try to extract the value immediately. After the next loop, the roll is available, so ZeroFrame can extract the value, then.
Here is another example in a templated output with various usages. Note the way this can both re-use rolls (with hover tips) in a simple message, and how it can extract the values from rolls that aren't available until later passes of the loop. Finally, note that the $[[3]]
roll does not exist in this command line, so when ZeroFrame goes to release the message, the $[[3]].value
construction is replaced with a 0:
!&{template:default}{{name=Roll Proof}}{{Roll0=[[1d20]]}}{{Roll 1=[\][\]2d20\]\]}}{{Roll 2=[\\][\\]3d20\\]\\]}}{{Re-Use 0=$[[0]]}}{{Re-Use 1=$[[1]]}}{{Re-Use 2=$[[2]]}}{{Value 1=$[[1]].value}}{{Value 2=$[[2]].value}}{{Non-Existent 3=$[[3]].value}}{&simple}
[edit] Nesting Inline Rolls
As just demonstrated, ZeroFrame takes advantage of its loop to give you the ability to nest your reused inline rolls. We can take this a step further and re-use our rolls in other rolls. For instance, we know that the first inline roll detected by the Roll20 parser can be reused in the command line by using the $[[0]]
marker. However, using that marker nested inside another inline roll would break the Roll20 parser and throw an error:
[[ $[[0]]d10 ]]
(this breaks in a normal chat call!)
We can make this sort of usage work with ZeroFrame with one minor change of escaping the outer roll as mentioned above:
[\][\] $[[0]]d10 \]\]
When ZeroFrame sees a nested roll such as this (one roll marker used in another roll), it will automatically extract the roll's value and insert it into the command line in place of the roll marker. You do not need to use a .value
structure.
Since the outer roll is dependent on the inner roll's value, we must slow it down by one loop (one deferral). Specifically, we must slow it down so that it only becomes detectable as an inline roll one loop cycle after the nested roll is available. In the above case, we are assuming that the $[[0]]
roll is available in the first loop pass, so we only need to slow down the outer roll by one pass to allow ZeroFrame the opportunity to extract the roll value.
Nest multiple levels of inner rolls by using 1-more escape character for each outer wrapping of inline roll structure, as mentioned above in the section Escaping and Deferring Text (Delayed Processing).
[edit] Delaying a Message
Use the {&delay}
tag to delay processing of a given message. Use a number to denote the number of seconds to delay:
!This message will delay for 2.4 seconds.{&simple}{&delay 2.4}
The delay tag is detected prior to all other tags ({&stop}
, {&simple}
, {&global}
, {&escape}
, etc.) except for {&skip}
, meaning that the delay can give game elements time to change prior to retrieval for the command line. For instance, if a delayed line contained a Fetch reference to a character attribute, the attribute could change during the span of the delay prior to Fetch going out to retrieve the data.
Note that Roll20 constructions cannot be delayed, since these will occur prior to ZeroFrame getting a chance to act on the message. Inline rolls, queries, and attribute/ability/macro retrieval will all happen prior to the ability to delay the message.
For this reason, if you need to escape certain constructions until the message has been properly delayed, the delay tag also allows for the declaration of a deferral string. Simply include the characters to be used as the deferral within parentheses attached to the end of the word "delay" in the delay tag: {&delay(^) 2.4}
. That will remove the ^ character anywhere within the command line at the point that the delay has finished running. Hide Roll20 constructions by interrupting the characters in the usage string, for instance:
@^{Bob the Hirsute|ac}
Roll20 references to the selected token will not work as a delayed retrieval:
@^{selected|ac}
...since the delayed message will not have selected tokens. For this reason, use Fetch. Batched lines (see Batching Sub-Commands, below), that are individually delayed will delay all subsequent commands.
[edit] Batching Sub-Commands (Sharing Rolls Across Messages)
Roll20 gives you the opportunity to create "macros" of commands. These can either be stored as Collections Tab Macros or as Ability Macros (on a character sheet). However, sometime in the recent past the community started reporting that commands would be randomly dropped from multi-command messages, making the basic functionality unreliable if not unusable. With ZeroFrame's batching ability, it restores this basic Roll20 multi-line functionality, and it delivers certain new benefits.
[edit] Basic Syntax
If you have a series of commands you have to execute together, simply wrap your existing macro like this:
!{{ ...commands here... }}
For instance, your finished macro might look like this:
!{{ !token-mod --set bar1|-1 !Spawn --name|Giant Ape --offset|1,0 }}
Any command that begins with a bang (e.g., !
) will be dispatched as a mod script message intended for the Script Moderator. If a command does not start with a bang, ZeroFrame will assume you want that particular line sent as a chat-output message (not intended for the Script Moderator), however it structure the outbound line by wrapping it in a bang and a {&simple}
:
!{{ This will hit chat. }}
In that example, the line This will hit chat.
will actually be dispatched as:
!This will hit chat.{&simple}
This gives you the opportunity for a separate ZeroFrame loop when the new message is caught.
[edit] Deferral Characters
Batched commands give you two more ways to defer constructions, both of them related to making a construction available only when an individual sub-command is dispatched as its own message.
[edit] Defer All
To declare a string of characters as your deferral string and have it apply to every command in the set of batched commands, enclose it in parentheses immediately following the opening double-brace:
!{{(^) ... ... }}
That will remove the ^
character from every command line prior to sending.
[edit] Defer For Single Line
To use a deferral string for only a single command line, enclose it in parentheses and include it at the start of the line (prior to any characters other than spaces):
!{{ (^)... ... }}
That would, for only the one line, remove any ^
characters found.
[edit] Considerations
You can use both kinds of escape characters (escaping for all lines and separately for a given line) together. Both escaping replacements will be applied (the escape-all deferral will happen, followed by any local deferral for a given line).
Deferral strings (in either location) can be more than a single character:
!{{(defer) !script --@defer(selected.attribute) }}
[edit] Example 1
This uses deferrals to make sure the roll in the second command isn't evaluated until the line is dispatched:
!{{ !scriptOne --args (^)!scriptTwo --roll [^[^2d20^]^] }}
[edit] Example 2
Sabor has the ability to transfer health from one familiar to the other. The names of each familiar are stored in attributes on Sabor's character sheet (FamiliarOne, FamiliarTwo). This is the macro that would send the health to his first familiar. Each command in this set requires a different token to be selected when the command is evaluated (using the {&select ...}
syntax of SelectManager).
!{{(^) !script --health|+1 {^&select @(@{Sabor|FamiliarOne}.token_id)} !script --health|-1 {^&select @(@{Sabor|FamiliarTwo}.token_id)} }}
(Requires SelectManager and Fetch)
[edit] Handling Double-Braces in Sub-Commands
Do to a quirk of Roll20 parsing that happens before ZeroFrame could even see the message, double-braces in sub-commands had been initially problematic for ZeroFrame batching operations (the message would be broken apart at the Roll20 level). As of v1.1.1, ZeroFrame now handles double-braces in sub-commands by giving you a special replacement character string.
If you have a sub-command that would include double-braces (either a template command or a mod script command that utilizes template syntax to, itself, break across multiple lines), use this replacement pattern:
{{ => ({) }} => (})
For example, here is how to embed a multi-line script command line within a batch:
!{{ !script ({) --arg1|foo --arg2|bar (}) ... ... }}
If one of your sub-commands uses a roll template, you should defer the &{template:...}
formation using some form of line deferral so that the template declaration does not get recognized and applied to the overall message (this would cause unexpected results). Deferral might look like this:
!{{ (^)&^{template:default} ({)name=Deferred Template(})({) ... (}) ... }}<pre> Similarly, if you use a ZeroFrame TEMPLATE tag intended for a sub-command, you should defer it, as well: <code>{^&template:...}</code> <pre> !{{ {&if @{selected|bar1} >= 0} (^){^&template:default}({)name=Deferred Template(})({) ... (}) {&else} (^){^&template:npcaction}({) ... (})({) ... (}) {&end} ... ... }}
That example tests the value of bar1 for a selected token, then it runs one template for a true result and another for a false result. Both templates are deferred, which means -- so far -- that the deferral character could have been declared as a batch-level deferral, immediately following the opening double-brace, like this: !{{(^)
. If that character becomes necessary to another line in the batch (what is elided in the ellipsis), then a different character series could be used or the necessary lines which require construction deferral could have their own characters declared (as above).
[edit] Re-Use Rolls Across Command Lines
One of the benefits of batching multiple command lines is the ability to use rolls in different command lines. Since your original message hits the parser as a single command line (before ZeroFrame separates individual commands), all of the rolls present in any of your individual commands are evaluated at once. ZeroFrame continues to make them available to all of the individually-dispatched messages.
!{{ Spanklefist attacks Bobbidangler and rolls [[1d20]]. {&if $[[0]] > 10} ... }}
In that example, the roll is first made as a statement that will be sent to chat announcing the attempted attack, then the roll is re-used in the conditional block on a completely separate line.
[edit] One Caveat to Sharing Rolls
Since all of your individual sub-command lines within a batch are initially treated as a single message, all of those lines are now referring to the same pool of shared inline rolls. So if you were converting a macro that initially started like this:
!script1 --arg1 [[1d20]] !script2 --arg1 [[[[1d10]]d4]] --arg2 $[[0]]
...you should be aware that the $[[0]]
of the second line would have referred only to its own command line previously, but in a batch it will refer to the overall pool of rolls. In this batch, the 0th roll will actually be the roll in the first sub-command line (associated with script1
).
You can fix this by incrementing roll indices when you put them into batch:
!{{ !script1 --arg1 [[1d20]] !script2 --arg1 [[[[1d10]]d4]] --arg2 $[[1]] }}
[edit] Re-Use All Meta Process Constructions (Definitions, Logic, Mules, etc.)
In a similar vein, since the entire batch of multiple sub-commands is initially treated as a single message, it will pass through a ZeroFrame loop. This means that just as you can re-use rolls across multiple command lines you can use meta-constructions in the same way.
This goes for APILogic definitions, conditionals, Muler mules, SelectManager select statements, and any other meta-operation that might be later added to the metascript toolbox.
[edit] The GLOBAL Tag (Declaring Variables)
ZeroFrame allows you to declare "global" variables which are then used in text replacement operations throughout the command line at the start of every loop cycle. Global variables are declared using the GLOBAL tag:
{&global ... }
Within that construction, you can include as many term definitions as you need. A term definition looks like this:
([term] definition)
Therefore a global variable defining theColor
to be red
would look like this:
{&global ([theColor] red)}
Here is an example with two definitions in the same tag:
{&global ([theColor] red) ([theRoll] 2d20kh1)}
[edit] Use Unique Variable Names
Take care that your variables (your terms) are unique enough that they will not catch occurrences you do not intend them to. For instance, if you were defining a series of rolls, a construction pattern of Roll1
, Roll2
, Roll3
, etc., might initially work. However, once you got up to defining Roll10
, you would encounter the problem that Roll1
searches would find Roll10
usages. ZeroFrame would replace the "Roll1" portion of "Roll10", leaving the definition in place, followed by the trailing "0".
[edit] Multiple GLOBAL Tags and Overwriting Variables
You can have as many GLOBAL tags in your command line as you see fit, and as many definitions per tag as you require. You can also utilize the tag in any loop cycle (either by deferring it to later detection or by porting it in via some retrieval operation).
All global definitions are stored in the same pool, so detected definitions are added as they are found, then they are used against the command line in a series of replacements. If you redefine the same term in a subsequent definition or GLOBAL tag, the value is altered for future cycles of the loop.
[edit] Loop Considerations
As mentioned, you can include a GLOBAL tag in any loop, and the detected definitions will apply for all future loop cycles. This means that if you define a global variable in the intial loop, then retrieve a segment of the command line in the fifth pass of the loop, ZeroFrame will still look for the term you defined and replace it with your supplied definition.
Global variables defined in later cycles can help you properly refer to rolls, since by the time your command line enters more and more cycles, you might have introduced multiple new rolls. Or you might have introduced a different number of rolls from a previous usage of the same command line. How do you know whether you're working with the 11th roll or the 4th? You can define a unique term to refer to the roll:
{&global ([metamorphRoll] [[1d20 - @{Morpho|metamorph_skill}]])}
Global variables are detected at the start of each loop, prior to the installed metascripts. This is important to remember if you bring in a GLOBAL tag during a particular cycle: ZeroFrame will not recognize the definitions and will not search/replace them in the command line until the start of the subsequent loop. For example, consider this command line snippet:
{&global ([theRoll] 1d20)} {&if theRoll > 10} ... {&else} ... {&end}
That defines a global variable (theRoll
), then uses the result of that roll to test in a conditional. If this snippet were a part of the command line from the initial loop, everything would function as you expect: the global variable is caught at the beginning of the loop and ZeroFrame performs the text-replacement prior to APILogic having a chance to evaluate the conditional.
On the other hand, if you used something like Fetch to return this snippet to your working command line during a subsequent loop, things will not work as written. Fetch would run before APILogic, but after the opportunity to detect the GLOBAL tag. So while Fetch would return the snippet with the GLOBAL tag present in it, the definition the GLOBAL tag contains would not be detected before APILogic. APILogic still has a chance to examine the command line during this cycle, before the cycle begins again and GLOBAL tags are detected again. In a case like this, you could defer the APILogic constructions with ZeroFrame loop deferrals (backslashes):
{&global ([theRoll] 1d20)} {\&if theRoll > 10} ... {\&else} ... {\&end}
Note: you have to defer all of the APILogic conditional constructors in the set.
[edit] Logging and Troubleshooting
If you need to troubleshoot a line to determine why it isn't evaluating or behaving the way you think it should, or if you just want to see how a command line changes throughout the loop process, include the {&log}
token in your command line:
!somescript --arg1|[[1d10]].value {& log}
With that tag included, ZeroFrame will track how the command line changes as it is handed off to each script and across loops. At the end, it will show you an output that lists those changes so you can see how pieces are resolved.
Each dot represents something different.
Orange => Loop Iteration Blue => Nothing detected/changed during this loop Green => Something detected, successfully changed Red => Something detected, could not resolve during this pass of the loop
[edit] Unresolved Syntax and Stopping Endless Loops
If a metascript can't resolve a token for which it is responsible, it will raise an 'unresolved' flag and leave the structure in place (for the most part). The loop will operate again to see if another metascript might alter the command line in such a way that the token can be resolved. Because of this, we run the risk of infinite loops.
Certain Mule variable retrieval patterns can also cause infinite loops. If the result of a variable retrieval is, itself, a get.variable statement, which resolves into another get.variable, etc., the process runs the risk of never ending. Similarly, expanding inline rolls against tables which include the syntax to trigger another inline roll against the table, could create a series of inline roll resolutions that would never end.
ZeroFrame stops this by comparing the command lines at each pass of the loop to those that have come before to find duplicates. After a certain number of these identical command lines are detected, ZeroFrame ends it processing (leaving some syntax structures in place), and provides you the log screen so you can see what happened.
[edit] Meta-Toolbox Examples
Examples using one or more Meta-Toolbox APIs
- Managing PathFinder Sizes with TokenMod and Metascripts (Muler + TokenMod, extra stuff w/ ZeroFrame, SelectManager, Fetch)
- Using TokenMod to update multiple token tooltips (TokenMod + SelectManager + Fetch + ZeroFrame)
- Macro to read current face of Rollable Token
- Repeating Sections & ChatSetAttr(Forum)
- First example: Plugger and SelectManager; Second example: Plugger, Muler, SelectManager, and ZeroFrame.
- Using MathOps standalone, more details(Forum)
- API Logic and Libinline(Forum)
- selectmanager example(Forum)
- ZeroFrame example with ColorEmote(Forum)
- Table example with Muler & ZeroFrame(Forum)
- Muler + more example(Forum)
- Dealer -- Is there a way to deal to a player, even if that player's token is not selected?(Forum) - Selectmanager example
- Audio Master API(Forum) - fixes with MathOps & Muler
- Fix "It's a Trap" with selectmanager(Forum)
- Search Attributes(Forum) - InsertArg example
- Is there an API that can roll saves for groups then apply a condition to the tokens that failed?(Forum) - selectmanager example
- TokenMod API - how to select token without clicking it(Forum) selectmanager fix TokenMod limitation
- default token size fix(Forum) Muler & Fetch examples
- Multi-step table resolution (Pacesetter system)(Forum) - Muler with ZeroFrame example
- Creating a HUD(Forum) - ZeroFrame, Fetch example working with TokenMod
- Retrieving next item in list(Forum) - Muler, ZeroFrame, and APILogic example
- Retrieving or assigning individual data to/from many tokens with deferred inline rolls(Forum) - examples of SelectManager's forselected handle using various metascripts with TokenMod