Introduction to Macro Branching: Difference between revisions
m (Text replacement - "source>" to "syntaxhighlight>") |
|||
(22 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
[[Category:MapTool]][[Category:Tutorial]]{{Intermediate}} | |||
==Introduction== | ==Introduction== | ||
When you write a macro, you'll frequently find yourelf wanting to either repeat an operation several times, or to choose from several options based on the outcome of a macro command. In game terms, you might want to make | When you write a macro, you'll frequently find yourelf wanting to either repeat an operation several times, or to choose from several options based on the outcome of a macro command. In game terms, you might want to make a damage roll several times in a row (say, one time for each enemy caught by a grenade blast), or you might want your macro to give you a damage roll ''if'' you hit, and say {{code|You missed!}} if you miss. | ||
In programming jargon, those concepts are called ''looping'' (where you go through a process repeatedly, or "loop through it"), and ''branching'' (where your program - in this case, the macro - "branches" down different paths). The MapTool macro language has several roll options (and one function) to let you branch and/or loop your commands. | In programming jargon, those concepts are called ''looping'' (where you go through a process repeatedly, or "loop through it"), and ''branching'' (where your program - in this case, the macro - "branches" down different paths). The MapTool macro language has several roll options (and one function) to let you branch and/or loop your commands. | ||
Line 13: | Line 14: | ||
We're going to get to using these options pretty fast, so I assume you've read the [[Introduction to Macro Writing]] and have knowledge of how to create a new macro and use some very basic commands in it (like creating a variable or a dice roll). | We're going to get to using these options pretty fast, so I assume you've read the [[Introduction to Macro Writing]] and have knowledge of how to create a new macro and use some very basic commands in it (like creating a variable or a dice roll). | ||
There are a couple concepts that should be introduced first, since they're going to be a great way to illustrate some of the branching concepts (and looping concepts, in the [[Introduction to Macro Loops]]. You'll get an explanation of the new concepts below. | There are a couple of concepts that should be introduced first, since they're going to be a great way to illustrate some of the branching concepts (and looping concepts, in the [[Introduction to Macro Loops]]). You'll get an explanation of the new concepts below. | ||
Also, don't forget to enable the ''Use ToolTips for Inline Rolls'' option in MapTool Preferences. | |||
==New Concept: Roll Options== | |||
MapTool's macro language presents the user with the ability to use both named functions - things with names like {{code|getProperty()}} or {{code|nextInitiative()}}, and ''Roll Options'', which are not functions but instead are special commands that are placed at beginning of a line of macro script. Roll Options are effectively "switches" or "toggles" that you set for a macro command that affect how MapTool will handle the commands contained within the line of macro code. A few simple roll options are mentioned in the [[Introduction to Macro Writing]] - things like {{code|[h:]}} and {{code|[e:]}} for hidden or expanded output, for example. However, there are more complex ones; and to use branching and looping you'll need to be familiar with them. | |||
Roll options must follow these rules: | |||
#Appear at the beginning of a macro command | |||
#If only one roll option is on the command, it ends with a colon. For example: {{code|[h:]}} | |||
#If multiple roll option are on the same command, they are separated by commas, and the last one is followed by a colon. For example, {{code|[h,if(HP > 0): command]}} | |||
#If a roll option takes an argument - that is, it has parentheses and wants you to put something in them, like a comparison - the colon (or comma, if there are multiple roll options) goes after the parentheses. | |||
==New Concept: The CODE Option== | ==New Concept: The CODE Option== | ||
Line 68: | Line 33: | ||
Normally, in any branching or looping technique, MapTool lets you do ''one thing'' - that is, one command. So if you had a statement that said "if a condition is true, do something cool," then "something cool" can only be one single thing - you might roll some dice, or assign a variable, or print out some text to the chat window. However, you couldn't roll some dice, assign a variable, assign ''another'' variable, do some math, and ''then'' print out something all in that statement. That's too many operations. | Normally, in any branching or looping technique, MapTool lets you do ''one thing'' - that is, one command. So if you had a statement that said "if a condition is true, do something cool," then "something cool" can only be one single thing - you might roll some dice, or assign a variable, or print out some text to the chat window. However, you couldn't roll some dice, assign a variable, assign ''another'' variable, do some math, and ''then'' print out something all in that statement. That's too many operations. | ||
If you could only do one thing when you branch or loop, macros would be very limited - so the macro language supports a special roll option called | If you could only do one thing when you branch or loop, macros would be very limited - so the macro language supports a special roll option called {{roll|code}}, which indicates to MapTool that you want to perform several different operations at once, but have them all be treated as a single unit (a single "branch" of a branching statement, or the body of the loop in a looping option). You would group these several commands inside a pair of curly braces ( { } ). | ||
The examples below will use the | The examples below will use the {{roll|code}} option, so you can see how it works. | ||
==New Concept: Comparison and Logical Operators== | ==New Concept: Comparison and Logical Operators== | ||
Line 90: | Line 55: | ||
===Comparisons=== | ===Comparisons=== | ||
The symbols below are the comparison operators. Remember that you must always think of these comparisons from the reference point of the value on the ''left'' side. So, in the comparison {{code|value1 > value2}}, you read it based on the left side: "is {{code|value1}} greater than {{code|value2}}. This is the rule for comparisons in MapTool - the left side of the operator is the "point of view." | The symbols below are the comparison operators. Remember that you must always think of these comparisons from the reference point of the value on the ''left'' side. So, in the comparison {{code|value1 > value2}}, you read it based on the left side: "is {{code|value1}} greater than {{code|value2}}?". This is the rule for comparisons in MapTool - the left side of the operator is the "point of view." | ||
* '''==''': "is equal to; | * '''==''': "is equal to"; this is the operator you use to see if one value is equal to another. Be careful - it has ''two'' equals signs in a row (remember, one equal sign is already reserved for assigning values to variable). An example of this comparison would look like {{code|[if(hit == "yes", "you hit!", "you missed!")]}} | ||
* '''>''': "is greater than; use this to see if the value on the left side is greater than the value on the right. For example: {{code|[if(roll > 17, "Hit!", "Miss")]. You can put a number on the left side, like {{code|[if(17 > roll, "Miss", "Hit!")]}} (note that it basically reverses the first example, so you need to switch the true and false outputs). | * '''>''': "is greater than"; use this to see if the value on the left side is greater than the value on the right. For example: {{code|[if(roll > 17, "Hit!", "Miss")]}}. You can put a number on the left side, like {{code|[if(17 > roll, "Miss", "Hit!")]}} (note that it basically reverses the first example, so you need to switch the true and false outputs). | ||
* '''>=''': "is greater than or equal to"; use this to see if the value on the left side is greater than ''or equal to'' the value on the right. For example: {{code|[if(roll >= 17, "Hit!", "Miss")]}} | * '''>=''': "is greater than or equal to"; use this to see if the value on the left side is greater than ''or equal to'' the value on the right. For example: {{code|[if(roll >= 17, "Hit!", "Miss")]}} | ||
* '''<''': "is less than"; use this to see if the value on the left side is ''less than'' the value on the right. For example, {{code|[if(roll < 19, "Miss", "Hit!")]}}} | * '''<''': "is less than"; use this to see if the value on the left side is ''less than'' the value on the right. For example, {{code|[if(roll < 19, "Miss", "Hit!")]}}} | ||
* '''<=''': "is less than or equal to"; use this to see if the value on the left side is ''less than or equal to'' the value on the right. For example: {{code|[if(roll <= 18, "normal hit", "critical hit")]}} | * '''<=''': "is less than or equal to"; use this to see if the value on the left side is ''less than or equal to'' the value on the right. For example: {{code|[if(roll <= 18, "normal hit", "critical hit")]}} | ||
* !=: "is not equal to"; use this to compare whether the value on the left side is ''not equal to'' the value on the right. Note that this operator doesn't care what the values actually ''are'', only that they are ''not equal''. For example, {{code|[if(roll ! | * '''!=''': "is not equal to"; use this to compare whether the value on the left side is ''not equal to'' the value on the right. Note that this operator doesn't care what the values actually ''are'', only that they are ''not equal''. For example, {{code|[if(roll != 1, "Not a fumble", "You fumbled!")]}} | ||
===Logical=== | ===Logical=== | ||
Line 105: | Line 70: | ||
* '''&&''': "and"; use this if you want to make sure that two or more comparisons are ''all'' true. For example: {{code|[if(roll > 1 && roll < 20, "Hit", "Miss")]}} requires ''both'' comparisons to be true, for the whole comparison group to be true. In other words, the roll must be ''greater than 1'' '''and''' ''less than 20'' in order for it to be a hit. If both of those aren't true, the output is {{code|Miss}}. | * '''&&''': "and"; use this if you want to make sure that two or more comparisons are ''all'' true. For example: {{code|[if(roll > 1 && roll < 20, "Hit", "Miss")]}} requires ''both'' comparisons to be true, for the whole comparison group to be true. In other words, the roll must be ''greater than 1'' '''and''' ''less than 20'' in order for it to be a hit. If both of those aren't true, the output is {{code|Miss}}. | ||
** '''Remember: if you use &&, every part of the comparison statement must be true for the whole comparison to be true!''' | ** '''Remember: if you use &&, every part of the comparison statement must be true for the whole comparison to be true!''' | ||
* '''||''': "or"; use this if you want or need only one out of multiple comparisons to be true, in order for the whole thing to be true. For example, {{code|if(enemyHealth | * '''||''': "or"; use this if you want or need only one out of multiple comparisons to be true, in order for the whole thing to be true. For example, {{code|if(enemyHealth == "dead" || enemyHealth == "dying", "Don't kick a guy while he's down", "He's fair game")}}. In the example, if ''either'' condition is true (that is, if {{code|enemyHealth}} is "dead" ''or'' "dying") the entire comparison group is true. Only if ''neither'' comparison is true does the whole thing become false. | ||
** '''Remember: use || if you only need one out of several comparisons to be true''' | ** '''Remember: use || if you only need one out of several comparisons to be true''' | ||
==IF: Comparing Values== | ==IF: Comparing Values== | ||
One of the most elementary ways to branch any code is the use of the idea of ''if - then''. That is, ''if'' some comparison is true, ''then'' do something | One of the most elementary ways to branch any code is the use of the idea of ''if - then''. That is, ''if'' some comparison is true, ''then'' do something. You would use the ''if'' concept to say "If my attack hits, then show the damage result!" | ||
MapTool's macro language has two kinds of if - a function (a function is a pre-defined set of instructions that you can "call" by referring to it by name), and a roll option (a roll option is a "switch" or "toggle" that tells MapTool how to handle a command. | MapTool's macro language has two kinds of if - a function (a function is a pre-defined set of instructions that you can "call" by referring to it by name), and a roll option (a roll option is a "switch" or "toggle" that tells MapTool how to handle a command). | ||
===if() Function=== | ===if() Function=== | ||
Line 125: | Line 90: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[if(attackHits == "yes", "You hit!", "You missed")] | [if(attackHits == "yes", "You hit!", "You missed")] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 134: | Line 99: | ||
* Check the variable {{code|attackHits}} to see if it has the value "yes" | * Check the variable {{code|attackHits}} to see if it has the value "yes" | ||
* If it has the value "yes", then print {{code|You hit!}} to chat, or | * If it has the value "yes", then print {{code|You hit!}} to chat, or | ||
* If it does ''not'' have the value "yes", then | * If it does ''not'' have the value "yes", then print {{code|You missed}} to chat | ||
The ''value_if_true'' and ''value_if_false'' parts of the {{func|if}} statement can be text, dice roll commands (like 1d6 or 1d20), or variables. What they ''cannot'' be is variable assignments - that is, you can't write an {{func|if}} statement like this: | The ''value_if_true'' and ''value_if_false'' parts of the {{func|if}} statement can be text, dice roll commands (like 1d6 or 1d20), or variables. What they ''cannot'' be is variable assignments - that is, you can't write an {{func|if}} statement like this: | ||
<blockquote>< | <blockquote><syntaxhighlight lang="mtmacro"> | ||
[if(attackHits=="yes", output = "You Hit!", output = "You missed")] | [if(attackHits=="yes", output = "You Hit!", output = "You missed")] | ||
</ | </syntaxhighlight></blockquote> | ||
It may seem like a good idea, but it won't work - MapTool will give what's known as a ''null pointer exception'', and the macro will fail. However, there is a trick to get around that: since {{func|if}} is a function, and all functions - when they run - produce a ''value'', you can assign the ''result'' of it to a variable! You would do it like this: | It may seem like a good idea, but it won't work - MapTool will give what's known as a ''null pointer exception'', and the macro will fail. However, there is a trick to get around that: since {{func|if}} is a function, and all functions - when they run - produce a ''value'', you can assign the ''result'' of it to a variable! You would do it like this: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[output = if(attackHits=="yes", "You Hit!", "You missed")] | [output = if(attackHits=="yes", "You Hit!", "You missed")] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 155: | Line 120: | ||
* Second, assign that ''result'' to the variable {{code|output}}, which you can then use like any variable | * Second, assign that ''result'' to the variable {{code|output}}, which you can then use like any variable | ||
=== | ==={{roll|if}} Roll Option=== | ||
In addition to {{func|if}}, there is another way to employ the concept of "if-then" in macro code. The {{ | In addition to {{func|if}}, there is another way to employ the concept of "if-then" in macro code. The {{roll|if}} ''roll option''. Roll options are, as mentioned above, effectively "switches" or "toggles" that you set for a macro command that affect how MapTool will handle it. A couple of simple roll options are mentioned in the [[Introduction to Macro Writing]] - things like {{roll|h}} and {{roll|e}} for hidden or expanded output, for example. | ||
Roll options must follow these rules: | Roll options must follow these rules: | ||
# Appear at the beginning of a macro command | # Appear at the beginning of a macro command | ||
# If only '''one''' roll option is on the | # If only '''one''' roll option is on the command, it ends with a colon. For example: {{code|[h:]}} | ||
# If ''multiple'' roll option are on the same command, they are separated by commas, and the ''last'' one is followed by a | # If ''multiple'' roll option are on the same command, they are separated by commas, and the ''last'' one is followed by a colon. For example, {{code|[h,if(HP > 0): command]}} | ||
# If a roll option takes an ''argument'' - that is, it has parentheses and wants you to put something in them, like a comparison - the colon (or comma, if there are multiple roll options) goes ''after'' the parentheses. Look at the examples below to see how it's used. | # If a roll option takes an ''argument'' - that is, it has parentheses and wants you to put something in them, like a comparison - the colon (or comma, if there are multiple roll options) goes ''after'' the parentheses. Look at the examples below to see how it's used. | ||
To use the {{ | To use the {{roll|if}} option as a comparison, you must follow the format: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[if(comparison): command_if_true; command_if_false] | [if(comparison): command_if_true; command_if_false] | ||
</ | </syntaxhighlight></blockquote> | ||
Line 178: | Line 143: | ||
* '''Command_if_false''': this is the command to execute if false. This is an optional statement - if you want it to do nothing if the comparison is false, then leave off the semicolon and the {{code|command_if_false}} part entirely. | * '''Command_if_false''': this is the command to execute if false. This is an optional statement - if you want it to do nothing if the comparison is false, then leave off the semicolon and the {{code|command_if_false}} part entirely. | ||
An example of the use of the {{ | An example of the use of the {{roll|if}} roll option might be: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[h,if(attackHits == "yes"): output="You hit!"; output="You missed"] | [h,if(attackHits == "yes"): output="You hit!"; output="You missed"] | ||
Result of your attack: [r:output] | Result of your attack: [r:output] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 194: | Line 159: | ||
* It then prints a short line of text and the value of {{code|output}} to chat. | * It then prints a short line of text and the value of {{code|output}} to chat. | ||
You'll note that the first line - the line that uses if - has '''''two''''' roll options on the same line: {{ | You'll note that the first line - the line that uses if - has '''''two''''' roll options on the same line: {{roll|h}} and {{roll|if}}. You'll also see that they are separated by a comma, and the colon goes ''after'' the last roll option, and ''before'' the commands in the {{code|command_if_true}} and {{code|command_if_false}} sections. | ||
===IF and CODE=== | ===IF and CODE=== | ||
So what if you want to do more than one thing based on a comparison? Say, set a bunch of variables to a certain value? For that, you use the {{ | So what if you want to do more than one thing based on a comparison? Say, set a bunch of variables to a certain value? For that, you use the {{roll|code}} roll option. | ||
Like all roll options, {{ | Like all roll options, {{roll|code}} is put at the beginning of the line, separated from other roll options by a comma. Macro programming convention (that is, the way most macro writers seem to do it) is to put {{roll|code}} as the last roll option in the list. So, the general format you will see in a macro is likely to be: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[roll_option1, roll_option2, code: macro_commands] | [roll_option1, roll_option2, code: macro_commands] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
The second component of the {{ | The second component of the {{roll|code}} option is the curly bracket ({ }). You use these to enclose multiple commands as a single group. Remember the format of the {{roll|if}} roll option? | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[if(comparison): command_if_true; command_if_false] | [if(comparison): command_if_true; command_if_false] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Well, the {{ | Well, the {{roll|code}} option lets you replace {{code|command_if_true}} and {{code|command_if_false}} with ''multiple'' macro commands. Let's look at an example: | ||
Suppose we write a macro to look at a variable called {{code|attackRoll}}. We want to compare it to a number (the target number), which is held by the variable {{code|targetNumber}}. Here's what we want the macro to do: | Suppose we write a macro to look at a variable called {{code|attackRoll}}. We want to compare it to a number (the target number), which is held by the variable {{code|targetNumber}}. Here's what we want the macro to do: | ||
Line 239: | Line 204: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[h:attackRoll = 1d20] | [h:attackRoll = 1d20] | ||
[h:targetNumber = 15] | [h:targetNumber = 15] | ||
Line 255: | Line 220: | ||
[attackRecharge = 3] | [attackRecharge = 3] | ||
[damageRoll = "no"] | [damageRoll = "no"] | ||
} | }] | ||
Your attack [attackResult], and you do [damageRoll] damage. Your attack will recharge in [attackRecharge] rounds. | Your attack [attackResult], and you do [damageRoll] damage. Your attack will recharge in [attackRecharge] rounds. | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 265: | Line 230: | ||
* We declare two variables, {{code|attackRoll}} and {{code|targetNumber}}, and give them initial values (in this case, {{code|attackRoll}} will be the result of a 1d20 roll, and {{code|targetNumber}} is set to 15). | * We declare two variables, {{code|attackRoll}} and {{code|targetNumber}}, and give them initial values (in this case, {{code|attackRoll}} will be the result of a 1d20 roll, and {{code|targetNumber}} is set to 15). | ||
* We set up the comparison (putting an h, in front - remember, that will hide the results from chat, so you don't see all the calculations in the if statement). | * We set up the comparison (putting an h, in front - remember, that will hide the results from chat, so you don't see all the calculations in the if statement). | ||
* We put {{ | * We put {{roll|code}} in there to warn MapTool that each part of the {{roll|if}} roll option - {{code|command_if_true}} and {{code|command_if_false}} - will actually consist of multiple separate commands. | ||
* We put a colon after the word {{code|code}}, to mark off the end of all the roll options. There is only ONE colon in the line! | * We put a colon after the word {{code|code}}, to mark off the end of all the roll options. There is only ONE colon in the line! | ||
* We use a { to mark the start of the {{code|command_if_true}} portion of the IF statement. We then put in our commands, each one separately and enclosed in square brackets. Once finished, we ''close'' that section of the IF statement with a }, and put a semicolon on the end (remember, the IF roll option needs a semicolon to separate {{code|command_if_true}} from {{code|command_if_false}}. | * We use a { to mark the start of the {{code|command_if_true}} portion of the IF statement. We then put in our commands, each one separately and enclosed in square brackets. Once finished, we ''close'' that section of the IF statement with a }, and put a semicolon on the end (remember, the IF roll option needs a semicolon to separate {{code|command_if_true}} from {{code|command_if_false}}. | ||
Line 274: | Line 239: | ||
'''NOTE''': The CODE roll option only works with ''other roll options''. You would not use this with the {{func|if}} ''function''. That is a bit confusing, but just remember: CODE only goes with other roll options. | '''NOTE''': The CODE roll option only works with ''other roll options''. You would not use this with the {{func|if}} ''function''. That is a bit confusing, but just remember: CODE only goes with other roll options. | ||
====-- Known Problems==== | |||
* '''Number of () levels''' | |||
The {{code|[if():]}} doesn't allow more than one level of {{code|()}}. So, | |||
<syntaxhighlight lang="mtmacro" line> | |||
[R, if(((1))): "true";"false"] | |||
</syntaxhighlight> | |||
will give an error.<br> | |||
* '''Help! There are ' ' in the output''' | |||
Note that currently | |||
<syntaxhighlight lang="mtmacro">[r,if(val == something),CODE:{Print something}]</syntaxhighlight> | |||
will produce extraneous single quotes in the output when the condition is false. | |||
The workaround for this is to add an empty block for the false side: | |||
<syntaxhighlight lang="mtmacro">[r,if(val == something),CODE:{Print something};{}]</syntaxhighlight> | |||
==SWITCH: Choosing from Many Options== | ==SWITCH: Choosing from Many Options== | ||
The {{func|if}} function and the {{ | The {{func|if}} function and the {{roll|if}} roll option both let you pick from two options - either do something when the comparison is ''true'', or do something different when the comparison is ''false''. But life - and RPG's - are not always so black and white. When you want to do different things based on one of ''many'' options, you use the {{roll|switch}} roll option. | ||
The general format is: | The general format is: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[switch(val): | [switch(val): | ||
case case_value1: command_1; | case case_value1: command_1; | ||
case case_value2: command_2; | case case_value2: command_2; | ||
case case_value3: command_3; | case case_value3: command_3; | ||
default: command_Default | default: command_Default] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 307: | Line 288: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[h:class = "Rogue"] | [h:class = "Rogue"] | ||
Line 315: | Line 296: | ||
case "Wizard": Armor = 1; | case "Wizard": Armor = 1; | ||
case "Priest": Armor = 4; | case "Priest": Armor = 4; | ||
default: Armor = 0 | default: Armor = 0] | ||
Your Armor Value is [Armor]. | Your Armor Value is [Armor]. | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 331: | Line 312: | ||
===SWITCH and CODE=== | ===SWITCH and CODE=== | ||
The | The {{roll|code}} option can be used with a {{roll|switch}} option, in a similar manner as {{roll|if}}. There are a couple tricky bits, but if you follow the pattern given in the examples, it should work for you. | ||
To do a | To do a {{roll|switch}} option with {{roll|code}}, the general format is: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[switch(val),code: | [switch(val),code: | ||
case case_1: { commands_for_case_1}; | case case_1: { commands_for_case_1}; | ||
case case_2: { commands_for_case_2}; | case case_2: { commands_for_case_2}; | ||
case case_3: { commands_for_case_3}; | case case_3: { commands_for_case_3}; | ||
default: { commands_for_default} | default: { commands_for_default}] | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 348: | Line 329: | ||
<blockquote> | <blockquote> | ||
< | <syntaxhighlight lang="mtmacro"> | ||
[h,switch(class),code: | [h,switch(class),code: | ||
case "Warrior": | case "Warrior": | ||
Line 374: | Line 355: | ||
[Armor = 0] | [Armor = 0] | ||
[beginningPowers = "Fists, Feet"] | [beginningPowers = "Fists, Feet"] | ||
} | }] | ||
Your Armor Value is [Armor] and your beginning powers are [beginningPowers]. | Your Armor Value is [Armor] and your beginning powers are [beginningPowers]. | ||
</ | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
Line 383: | Line 364: | ||
Also, you'll see that I've added in some line breaks so that each separate group of operations is easier to read - MapTool is cool with that, because extra line breaks ''inside'' a command (remember, commands are enclosed within '''[ ]''') are ignored. This is nice, because it makes the macros ''much'' easier to read. | Also, you'll see that I've added in some line breaks so that each separate group of operations is easier to read - MapTool is cool with that, because extra line breaks ''inside'' a command (remember, commands are enclosed within '''[ ]''') are ignored. This is nice, because it makes the macros ''much'' easier to read. | ||
'''Notes''' | |||
* the {{code|case}} and {{code|default}} statements are the only case-sensitive statements in MapTool, thus {{code|CASE}} or {{code|Default}} will NOT work! | |||
* strings in the {{code|case}} MUST be surrounded by "double quotes" as 'single quotes' will generate an error. | |||
* do not put a {{code|;}} after your last case. | |||
==Advanced Branching Options== | ==Advanced Branching Options== | ||
The two options illustrated above are the most common branching options used in macro writing. However, they are not the ''only'' options for branching macros - there are two others, which involve either leaving one macro entirely to call on another, or changing the focus (that is, what token is the [[Current Token]]) of a macro temporarily. Since these are fairly complex operations all on their own, you'll find them in the [[More Branching Options]] guide. | The two options illustrated above are the most common branching options used in macro writing. However, they are not the ''only'' options for branching macros - there are two others, which involve either leaving one macro entirely to call on another, or changing the focus (that is, what token is the [[Current Token]]) of a macro temporarily. Since these are fairly complex operations all on their own, you'll find them in the [[More Branching Options]] guide. |
Latest revision as of 23:59, 14 March 2023
INTERMEDIATE
THIS IS AN INTERMEDIATE ARTICLE
Introduction
When you write a macro, you'll frequently find yourelf wanting to either repeat an operation several times, or to choose from several options based on the outcome of a macro command. In game terms, you might want to make a damage roll several times in a row (say, one time for each enemy caught by a grenade blast), or you might want your macro to give you a damage roll if you hit, and say You missed!
if you miss.
In programming jargon, those concepts are called looping (where you go through a process repeatedly, or "loop through it"), and branching (where your program - in this case, the macro - "branches" down different paths). The MapTool macro language has several roll options (and one function) to let you branch and/or loop your commands.
Finally, because there are a lot of times when you'll want to do several things at the same time when you branch or loop, there's a special roll option called code that tells MapTool to treat several macro commands as a single "unit" when you loop or branch. That may sound confusing, but you'll see what it all means shortly!
Since there's a lot of ground to cover, this tutorial will cover branching (running different commands based on some condition). The Introduction to Macro Loops will handle looping (running a process repeatedly, until you wish it to stop).
Assumptions
We're going to get to using these options pretty fast, so I assume you've read the Introduction to Macro Writing and have knowledge of how to create a new macro and use some very basic commands in it (like creating a variable or a dice roll).
There are a couple of concepts that should be introduced first, since they're going to be a great way to illustrate some of the branching concepts (and looping concepts, in the Introduction to Macro Loops). You'll get an explanation of the new concepts below.
Also, don't forget to enable the Use ToolTips for Inline Rolls option in MapTool Preferences.
New Concept: Roll Options
MapTool's macro language presents the user with the ability to use both named functions - things with names like getProperty()
or nextInitiative()
, and Roll Options, which are not functions but instead are special commands that are placed at beginning of a line of macro script. Roll Options are effectively "switches" or "toggles" that you set for a macro command that affect how MapTool will handle the commands contained within the line of macro code. A few simple roll options are mentioned in the Introduction to Macro Writing - things like [h:]
and [e:]
for hidden or expanded output, for example. However, there are more complex ones; and to use branching and looping you'll need to be familiar with them.
Roll options must follow these rules:
- Appear at the beginning of a macro command
- If only one roll option is on the command, it ends with a colon. For example:
[h:]
- If multiple roll option are on the same command, they are separated by commas, and the last one is followed by a colon. For example,
[h,if(HP > 0): command]
- If a roll option takes an argument - that is, it has parentheses and wants you to put something in them, like a comparison - the colon (or comma, if there are multiple roll options) goes after the parentheses.
New Concept: The CODE Option
Normally, in any branching or looping technique, MapTool lets you do one thing - that is, one command. So if you had a statement that said "if a condition is true, do something cool," then "something cool" can only be one single thing - you might roll some dice, or assign a variable, or print out some text to the chat window. However, you couldn't roll some dice, assign a variable, assign another variable, do some math, and then print out something all in that statement. That's too many operations.
If you could only do one thing when you branch or loop, macros would be very limited - so the macro language supports a special roll option called [code():], which indicates to MapTool that you want to perform several different operations at once, but have them all be treated as a single unit (a single "branch" of a branching statement, or the body of the loop in a looping option). You would group these several commands inside a pair of curly braces ( { } ).
The examples below will use the [code():] option, so you can see how it works.
New Concept: Comparison and Logical Operators
In macro writing, you're going to want to compare values together a lot - is my dice roll greater than 20? Are my hit points less than 0? Does that weapon name equal "Warhammer?" All of these are handled via comparison operators and logical operators.
Comparison Operator is programming jargon for the symbols we use to have MapTool compare two values to each other in certain ways (an operator is a symbol that performs an operation - for instance, the + symbol is an operator that adds things together).
A Logical Operator is a symbol you use to instruct MapTool in what order to consider comparisons, and how to group comparisons together. The comparison and logical operators are described below:
In the examples below, the if() function is used to illustrate the examples. It's described in more detail later, but the basic "format" of the if() function is this:
if(comparison, value_if_true, value_if_false)
- Comparison is where you do your actual comparison (greater than, less than, etc.)
- Value_if_true is where you put the output or value if the comparison is true
- Value_if_false is, obviously, where you put the output or value if the comparison is false
Comparisons
The symbols below are the comparison operators. Remember that you must always think of these comparisons from the reference point of the value on the left side. So, in the comparison value1 > value2
, you read it based on the left side: "is value1
greater than value2
?". This is the rule for comparisons in MapTool - the left side of the operator is the "point of view."
- ==: "is equal to"; this is the operator you use to see if one value is equal to another. Be careful - it has two equals signs in a row (remember, one equal sign is already reserved for assigning values to variable). An example of this comparison would look like
[if(hit == "yes", "you hit!", "you missed!")]
- >: "is greater than"; use this to see if the value on the left side is greater than the value on the right. For example:
[if(roll > 17, "Hit!", "Miss")]
. You can put a number on the left side, like[if(17 > roll, "Miss", "Hit!")]
(note that it basically reverses the first example, so you need to switch the true and false outputs). - >=: "is greater than or equal to"; use this to see if the value on the left side is greater than or equal to the value on the right. For example:
[if(roll >= 17, "Hit!", "Miss")]
- <: "is less than"; use this to see if the value on the left side is less than the value on the right. For example,
[if(roll < 19, "Miss", "Hit!")]
} - <=: "is less than or equal to"; use this to see if the value on the left side is less than or equal to the value on the right. For example:
[if(roll <= 18, "normal hit", "critical hit")]
- !=: "is not equal to"; use this to compare whether the value on the left side is not equal to the value on the right. Note that this operator doesn't care what the values actually are, only that they are not equal. For example,
[if(roll != 1, "Not a fumble", "You fumbled!")]
Logical
The symbols below are the logical operators. You use this to group comparisons together (you only need these if you need to make multiple comparisons at the same time). These go between individual comparisons (these don't replace the comparison operators above!).
- &&: "and"; use this if you want to make sure that two or more comparisons are all true. For example:
[if(roll > 1 && roll < 20, "Hit", "Miss")]
requires both comparisons to be true, for the whole comparison group to be true. In other words, the roll must be greater than 1 and less than 20 in order for it to be a hit. If both of those aren't true, the output isMiss
.- Remember: if you use &&, every part of the comparison statement must be true for the whole comparison to be true!
- ||: "or"; use this if you want or need only one out of multiple comparisons to be true, in order for the whole thing to be true. For example,
if(enemyHealth == "dead" || enemyHealth == "dying", "Don't kick a guy while he's down", "He's fair game")
. In the example, if either condition is true (that is, ifenemyHealth
is "dead" or "dying") the entire comparison group is true. Only if neither comparison is true does the whole thing become false.- Remember: use || if you only need one out of several comparisons to be true
IF: Comparing Values
One of the most elementary ways to branch any code is the use of the idea of if - then. That is, if some comparison is true, then do something. You would use the if concept to say "If my attack hits, then show the damage result!"
MapTool's macro language has two kinds of if - a function (a function is a pre-defined set of instructions that you can "call" by referring to it by name), and a roll option (a roll option is a "switch" or "toggle" that tells MapTool how to handle a command).
if() Function
The if() function is called simply by writing if()
and putting the thing you want compared, what to do if the comparison is true, and what to do if the comparison is false, all inside the parentheses. The general format is:
if(comparison, value_if_true, value_if_false)
An actual example would look like:
[if(attackHits == "yes", "You hit!", "You missed")]
In that single line, we've said:
- Check the variable
attackHits
to see if it has the value "yes" - If it has the value "yes", then print
You hit!
to chat, or - If it does not have the value "yes", then print
You missed
to chat
The value_if_true and value_if_false parts of the if() statement can be text, dice roll commands (like 1d6 or 1d20), or variables. What they cannot be is variable assignments - that is, you can't write an if() statement like this:
[if(attackHits=="yes", output = "You Hit!", output = "You missed")]
It may seem like a good idea, but it won't work - MapTool will give what's known as a null pointer exception, and the macro will fail. However, there is a trick to get around that: since if() is a function, and all functions - when they run - produce a value, you can assign the result of it to a variable! You would do it like this:
[output = if(attackHits=="yes", "You Hit!", "You missed")]
When you do it that way, MapTool will:
- First, decide what the result of the if() is, and
- Second, assign that result to the variable
output
, which you can then use like any variable
[if():] Roll Option
In addition to if(), there is another way to employ the concept of "if-then" in macro code. The [if():] roll option. Roll options are, as mentioned above, effectively "switches" or "toggles" that you set for a macro command that affect how MapTool will handle it. A couple of simple roll options are mentioned in the Introduction to Macro Writing - things like [h:] and [e:] for hidden or expanded output, for example.
Roll options must follow these rules:
- Appear at the beginning of a macro command
- If only one roll option is on the command, it ends with a colon. For example:
[h:]
- If multiple roll option are on the same command, they are separated by commas, and the last one is followed by a colon. For example,
[h,if(HP > 0): command]
- If a roll option takes an argument - that is, it has parentheses and wants you to put something in them, like a comparison - the colon (or comma, if there are multiple roll options) goes after the parentheses. Look at the examples below to see how it's used.
To use the [if():] option as a comparison, you must follow the format:
[if(comparison): command_if_true; command_if_false]
- Comparison: this is a comparison statement, as used in the if() above.
- Command_if_true: this is the command to execute if true; in this form of IF, you can do variable assignments or commands that you cannot do in the if() method. However, it doesn't have to be a whole command - it can still be a bit of text.
- Command_if_false: this is the command to execute if false. This is an optional statement - if you want it to do nothing if the comparison is false, then leave off the semicolon and the
command_if_false
part entirely.
An example of the use of the [if():] roll option might be:
[h,if(attackHits == "yes"): output="You hit!"; output="You missed"] Result of your attack: [r:output]
In the above example, the following things are happening:
- MapTool compares the value of
attackHits
to the value"yes"
- If the comparison is true - that is, the value of
attackHits
is indeed equal to"yes"
- it assigns the value"You hit!"
to the variableoutput
. - If the comparison is false - the value of
attackHits
is not equal to"yes"
- it assigns the value"You missed"
to the variableoutput
.
- If the comparison is true - that is, the value of
- It then prints a short line of text and the value of
output
to chat.
You'll note that the first line - the line that uses if - has two roll options on the same line: [h:] and [if():]. You'll also see that they are separated by a comma, and the colon goes after the last roll option, and before the commands in the command_if_true
and command_if_false
sections.
IF and CODE
So what if you want to do more than one thing based on a comparison? Say, set a bunch of variables to a certain value? For that, you use the [code():] roll option.
Like all roll options, [code():] is put at the beginning of the line, separated from other roll options by a comma. Macro programming convention (that is, the way most macro writers seem to do it) is to put [code():] as the last roll option in the list. So, the general format you will see in a macro is likely to be:
[roll_option1, roll_option2, code: macro_commands]
The second component of the [code():] option is the curly bracket ({ }). You use these to enclose multiple commands as a single group. Remember the format of the [if():] roll option?
[if(comparison): command_if_true; command_if_false]
Well, the [code():] option lets you replace command_if_true
and command_if_false
with multiple macro commands. Let's look at an example:
Suppose we write a macro to look at a variable called attackRoll
. We want to compare it to a number (the target number), which is held by the variable targetNumber
. Here's what we want the macro to do:
If attackRoll
is greater than or equal to targetNumber
, the macro should:
- Set
attackUsed
to "yes" - Set
attackResult
to "hits" - Set
attackRecharge
to 3 - Set
damageRoll
to the result of the dice roll 1d8+4. - Output a string telling the user the results.
If attackRoll
is not greater than or equal to targetNumber
, the macro should:
- Set
attackUsed
to "Yes" - Set
attackResult
to "misses" - Set
attackRecharge
to 3 - Set
damageRoll
to "no" - Output a string to chat telling the user the results.
Here's how to do it:
[h:attackRoll = 1d20] [h:targetNumber = 15] [h,if(attackRoll >= targetNumber), code: { [attackUsed = "yes"] [attackResult = "hits"] [attackRecharge = 3] [damageRoll = 1d8+4] }; { [attackUsed = "yes"] [attackResult = "misses"] [attackRecharge = 3] [damageRoll = "no"] }] Your attack [attackResult], and you do [damageRoll] damage. Your attack will recharge in [attackRecharge] rounds.
There's a lot going on here, but the important thing to look for is the CODE option in the very first line, and the curly braces. The curly braces enclose multiple separate commands, but say to MapTool, "treat these as one thing". So in the example above:
- We declare two variables,
attackRoll
andtargetNumber
, and give them initial values (in this case,attackRoll
will be the result of a 1d20 roll, andtargetNumber
is set to 15). - We set up the comparison (putting an h, in front - remember, that will hide the results from chat, so you don't see all the calculations in the if statement).
- We put [code():] in there to warn MapTool that each part of the [if():] roll option -
command_if_true
andcommand_if_false
- will actually consist of multiple separate commands. - We put a colon after the word
code
, to mark off the end of all the roll options. There is only ONE colon in the line! - We use a { to mark the start of the
command_if_true
portion of the IF statement. We then put in our commands, each one separately and enclosed in square brackets. Once finished, we close that section of the IF statement with a }, and put a semicolon on the end (remember, the IF roll option needs a semicolon to separatecommand_if_true
fromcommand_if_false
. - We do the same process for the
command_if_false
section - a { followed by a series of commands, and then closed with a }. - We make sure to close off the whole if statement with another square bracket ( ] ). Remember, an IF roll option is still just a macro command, and all macro commands must be enclosed in [ ].
- Finally, we write some text, with the several variables we have inserted at appropriate points, to be sent to chat when the macro runs.
NOTE: The CODE roll option only works with other roll options. You would not use this with the if() function. That is a bit confusing, but just remember: CODE only goes with other roll options.
-- Known Problems
- Number of () levels
The [if():]
doesn't allow more than one level of ()
. So,
[R, if(((1))): "true";"false"]
will give an error.
- Help! There are ' ' in the output
Note that currently
[r,if(val == something),CODE:{Print something}]
will produce extraneous single quotes in the output when the condition is false. The workaround for this is to add an empty block for the false side:
[r,if(val == something),CODE:{Print something};{}]
SWITCH: Choosing from Many Options
The if() function and the [if():] roll option both let you pick from two options - either do something when the comparison is true, or do something different when the comparison is false. But life - and RPG's - are not always so black and white. When you want to do different things based on one of many options, you use the [switch():] roll option.
The general format is:
[switch(val): case case_value1: command_1; case case_value2: command_2; case case_value3: command_3; default: command_Default]
What's happening here is this:
- MapTool is looking at the value of the variable
val
- MapTool then looks at each of the
case
statements in the switch, and comparesval
tocase_value1
,case_value2
, andcase_value3
- When MapTool finds a match - that is,
val
is equal to one of those cases, the appropriate command (eithercommand_1
,command_2
, orcommand_3
) is executed, and then MapTool exits the switch statement (which just means, once it's found a match, it does what that case says, and then stops checking for matches).
Suppose, for example, that the we wanted a macro that would automatically assign the right Armor
value to a token, based on the token's Class
. If you've been following along, you might recognize the Armor value as one of the attributes in the Sample Ruleset. If you visit the Sample Ruleset page, you'll see that a character can have one of several armor values, based on the character's class:
- A Warrior has an armor value of 6
- A Rogue has an armor value of 2
- A Wizard has an armor value of 1
- A Priest has an armor value of 4
So, let's say we want a macro to ask us for the value of the variable class
, and then use that variable to assign the right Armor
value. Here's how we'd do it:
[h:class = "Rogue"] [h,switch(class): case "Warrior": Armor = 6; case "Rogue": Armor = 2; case "Wizard": Armor = 1; case "Priest": Armor = 4; default: Armor = 0] Your Armor Value is [Armor].
What the above example does is:
- Look at the value for
class
- if you try this out, it will always show the value for "Rogue." If you alter the[h:class="Rogue"]
line, you can see how changing that value affects the switch statement). - Compare what you put in there with the four different cases - checking to see if
class
is equal to"Warrior"
,"Rogue"
,"Wizard"
, or"Priest"
. - If
class
equals any of those (and we mean EXACTLY equals - case sensitive, no spaces, an exact match), run the command to set the variableArmor
to the appropriate value. - If no match is found, do whatever follows the
default
option (in other words, setArmor
to 0. - Stop looking for matches, and move on.
SWITCH and CODE
The [code():] option can be used with a [switch():] option, in a similar manner as [if():]. There are a couple tricky bits, but if you follow the pattern given in the examples, it should work for you.
To do a [switch():] option with [code():], the general format is:
[switch(val),code: case case_1: { commands_for_case_1}; case case_2: { commands_for_case_2}; case case_3: { commands_for_case_3}; default: { commands_for_default}]
An actual example can be drawn from the Sample Ruleset as well. Not only does a character's class indicate his or her armor value, but also the list of "Beginning Powers" from which the character can draw. Suppose we wanted to set not only the armor value, but also a variable called beginningPowers
. To do that, you'd write a SWITCH that looks like:
[h,switch(class),code: case "Warrior": { [Armor = 6] [beginningPowers = "Sword, Shield Bash, Bow, Shield, Torch"] }; case "Rogue": { [Armor = 2] [beginningPowers = "Dagger, Hide, Backstab, Pick Lock, Torch"] }; case "Wizard": { [Armor = 1] [beginningPowers = "Dagger, Staff, Light, Lightning Bolt, Fire Ball"] }; case "Priest": { [Armor = 4] [beginningPowers = "Mace, Heal, Protect, Banish Undead, Torch"] }; default: { [Armor = 0] [beginningPowers = "Fists, Feet"] }] Your Armor Value is [Armor] and your beginning powers are [beginningPowers].
As you can see, each different case is treated as a single block of operations - so you need to put curly braces for each separate case, and separate them all with the semicolon. At the very end, we put a closing square bracket (]), to finish the whole command. Again, what has happened is that the CODE option and the curly braces have allowed you to replace a single command, like command_for_case_1
, with a group of commands.
Also, you'll see that I've added in some line breaks so that each separate group of operations is easier to read - MapTool is cool with that, because extra line breaks inside a command (remember, commands are enclosed within [ ]) are ignored. This is nice, because it makes the macros much easier to read.
Notes
- the
case
anddefault
statements are the only case-sensitive statements in MapTool, thusCASE
orDefault
will NOT work! - strings in the
case
MUST be surrounded by "double quotes" as 'single quotes' will generate an error. - do not put a
;
after your last case.
Advanced Branching Options
The two options illustrated above are the most common branching options used in macro writing. However, they are not the only options for branching macros - there are two others, which involve either leaving one macro entirely to call on another, or changing the focus (that is, what token is the Current Token) of a macro temporarily. Since these are fairly complex operations all on their own, you'll find them in the More Branching Options guide.