Introduction to Macro Loops: Difference between revisions
Line 153: | Line 153: | ||
Target 4: Attack 10; 3 damage.<br> | Target 4: Attack 10; 3 damage.<br> | ||
</blockquote> | </blockquote> | ||
==WHILE: Keep On Keepin' On== | |||
Let's move on to a new looping structure: {{code|[WHILE():]}}. This structure is the first one we'll discuss that uses a ''condition'' to determine how many times to loop (previously, count used a value - but not a comparison of any kind). The general format of a {{code|[WHILE():]}} loop is: | |||
<blockquote> | |||
<source lang="mtmacro"> | |||
[while(condition): command] | |||
</source> | |||
</blockquote> | |||
You're probably getting used to reading these by now. | |||
* '''WHILE()''': of course, we need to add the roll option itself | |||
* '''condition''': this is the comparison that we make, to see if the loop needs to stop - it can be any of the logical comparisons we've discussed already (such as, {{code|loops < 10}} or {{code|numDice > 5}} or anything you can think of). | |||
* '''command''': the macro command (or commands, if you use the CODE option) to run each time the loop goes 'round. | |||
So, if you read this, the while loop really just says, "while some condition is true, keep doing this." Let's look at some examples. | |||
===Example 1: Basic Countdown=== | |||
This is a very basic example, just to illustrate the basic parts of the WHILE() loop. Suppose you wanted to count down from 10 to 1. There are many ways to do this, of course, but we'll do it with a WHILE() loop. The macro would look like this: | |||
<blockquote> | |||
<source lang="mtmacro" line> | |||
[h:num = 10] | |||
[while(num > 0): num = num - 1] | |||
</source> | |||
</blockquote> | |||
The above macro simply says that "while the variable {{code|num}} is greater than 0, subtract 1 from {{code|num}}, and repeat." Each time through the loop, the value of {{code|num}} will be checked by MapTool. If it is greater than 0, it will let the command {{code|num {{=}} num-1}} happen, and display the result in chat; if {{code|num}} is ''not'' greater than 0, then the loop will be halted. The output of this macro looks like: | |||
<blockquote> | |||
9,8,7,6,5,4,3,2,1 | |||
</blockquote> | |||
You'll note that "10" was never shown. That's because while the loop may have started with {{code|num}} having the value of 10, the first time we ''see'' any output is when the operation {{code|num -1}} takes place - so the first thing we see is {{code|num - 1}}, which is 9. | |||
===Example 2: The Machine-Gun=== | |||
Let's look at a more complicated (and perhaps more interesting) example. In this example, we won't be using the [[Sample Ruleset]], mostly because I couldn't think of a useful example from that game. So, let's assume we have the following game situation: | |||
* A character has a machine gun with 30 rounds of ammunition | |||
* They may fire one to six rounds for every action | |||
* If their attack roll of 1d20 is greater than 15, they may make another attack; otherwise, they must end their turn. They always get at least 1 attack, though. | |||
So what we need to do is repeat the operation until ''either'' the weapon runs out of ammo, or they roll less than a 15 on their attack. Here's how that macro would look: | |||
<blockquote> | |||
<source lang="mtmacro" line> | |||
[h:ammo = 30] | |||
[h:hit = 1] | |||
[while(ammo > 0 && hit == 0),CODE: | |||
{ | |||
[h:attackRoll = 1d20] | |||
[h:ammoSpent = 1d6] | |||
[h,if(attackRoll > 15): hit = 1; hit = 0] | |||
[h:ammo = ammo - ammoSpent] | |||
Your first attack expends [r:ammoSpent] rounds, and [if(hit==1, "hits.", "misses.")] You have [r:ammo] rounds remaining. | |||
}] | |||
You have run out of ammo, or missed your target. Your turn is over. | |||
</source> | |||
</blockquote> | |||
Here's the breakdown of this macro: | |||
* Line 1 and 2 set two important variables: {{code|ammo}} and {{code|hit}}. We set {{code|ammo}} to 30, per the assumptions above, and we set {{code|hit}} to 1, so that the character always gets at least ''one'' attack roll (if we didn't set {{code|hit}} to 1, the loop might stop before it started!). | |||
* Line 4 is the start of the While Loop: we establish the loop, and give it a combined condition. We say that ''while'' {{code|ammo}} has a value greater than 30, ''and'' (remember, two ampersands is the logical operator "and") {{code|hit}} ''is equal to'' 1, the loop should go 'round. If ''either or both'' of those is ''not'' true, then the loop should stop. | |||
* Lines 5 - 9 handle the actual loop processing. Note that in that loop, we make sure to set a new value for {{code|hit}} and {{code|ammo}} - this is '''''critical'''''. If you never change the variables that your conditions are based on, then your loop will '''''never stop'''''. | |||
It's worth repeating: in a while loop, you '''''must change the variable that your condition is checking, or the loop will never stop'''''. |
Revision as of 01:59, 10 April 2009
Introduction
We've looked at branching in macros, using IF(), SWITCH(), and the more advanced roll options MACRO() and TOKEN(). Branching is one of the most important tools for macro writing, since it lets you automate decisions based on certain factors or conditions that arise during play.
Another common task in MapTool macros is to repeat a process multiple times - for example, you may want to repeat an attack roll several times, against multiple targets (for instance, if you threw a grenade, you might need to roll to see which targets are hit by the blast), or you may want to go through a list of skills, and print out the skill rating for each one. In both cases, you're repeating the same operation several times in a row, generating different results each time. In macro writing, we call this process looping (you may see it described in places as "looping through a list" or "iterating over an array" - regardless, the processes to do it are loops).
There are four loop structures in MapTool macros: COUNT(), FOR(), FOREACH(), and WHILE(). Each of the three is a roll option, which means - as we've seen before:
- It is placed at the beginning of a macro command
- It is followed by a colon
- If more than one roll option is used, they are separated by commas, and the last one is followed by a colon
- After the colon, you place the operation you want to do every time the loop runs
Before we continue, we'll need to introduce a couple concepts that will be used heavily in the examples below, especially for FOR
and FOREACH
loops.
New Concept: String Lists and String Properties
RPGs have a lot of information that goes into playing them - there are stats, and skills, and dice rolls, and weapons, and equipment, and powers and magic and...well, you name it, and there's an RPG out there that covers it.
For basic things, it might make sense to create a token property for each piece of information. In fact, you can do this for every possible bit of information you want to record about a character - but already, I bet you're thinking "that's a lot of properties"). And it is!
Fortunately, there are other ways to store information in MapTool properties (and macro variables) that let you group information together. The two new information storing methods we'll use - which are properly referred to as data types - are string lists and string properties.
String Lists
A string list is, first, a string - that is, a collection of alphanumeric text that is treated as just text (that is, it's not a number, so you can't add it to another number; it's not a dice roll, so MapTool won't automatically roll it; it's just a string of characters).
Second, it is a list - a collection of single items, separated by some sort of separating character - the (you guessed it) separator (also called a delimiter). The separator marks the beginning and end of an individual item in a list. This is a long way of saying "a string list is a single value that is a list of things, like colors: blue, green, red, orange, mauve."
Formally - in macro code - a string list looks like:
[h:listOfColors = "blue, green, red, orange, mauve"]
In the example, you'll see that we've made a variable assignment, and given the variable listOfColors
the values blue, green, red, orange, mauve
. The whole thing is enclosed in double quotes, which tells MapTool that it's a string, and from its very format, MapTool knows that it's a string list.
A string list can contain anything - it could be a list of names, of numbers, of dice rolls - anything you might want to keep a list of.
However! It is important to remember that no matter what each item in a string list is, it is always treated as a string. So if MapTool reads a string list that looks like "1d6, 2d8, 1d20"
, it will not see the first item and see a command to roll 1 six-sided die; instead it will see the character "1", the character "d", and the character "6", all put together. They may look the same to us, but they don't look the same to MapTool - to turn that "1d6" into 1d6
so that MapTool will roll it, we need to use the eval() function to tell MapTool "evaluate that string as if it were a dice roll." You'll see some examples of eval() later on.
String Properties
String properties are very similar to string lists - they are strings with special formatting, and they contain a collection of items. However, string properties have additional features that make them very useful for storing information in a different way.
The essence of a string property is the the key - value pairing. Basically, for each item in the string property, there is a key that is paired with a value. For instance, if you have a weapon with the following details:
- Weapon Name: Broadsword
- Weapon Damage Dice: 1d8
- Weapon Damage Type: Slashing
- Weapon Category: Versatile
You have a series of key - value pairs: the key "Weapon Name" is paired with the value "Broadsword," the key "Weapon Damage Dice" is paired with the value "1d8," and so on. A string property is simply a "formal" way to set up this kind of pairing in a single variable. The string property for the above weapon might read:
[h:weapon1 = "name=Broadsword; damageDice=1d8; damageType=Slashing; category=Versatile;"]
In that string property, the word to the left of the equal sign is the key, and the word to the right is the value. The semicolon is the separator or delimiter. MapTool has special functions to retrieve and change the values within a string property, which you'll see in use later.
Now, let's get to the loops!
COUNT: Over and Over and Over and...
The first looping structure we'll cover is the COUNT():
option. This option is the simplest loop - it repeats the operation following the colon a number of times equal to its argument (remember, arguments are the values or variables you put inside the parentheses). The format of a COUNT():
(which can also be abbreviated C():
statement is:
[count(repetitions): command]
or
[c(repetitions): command]
The example should be pretty self-explanatory. It contains:
- The
COUNT():
option - without the option, we wouldn't need this tutorial, right? repetitions
: this is the value that tellsCOUNT
how many times to repeatcommand
command
: this is the actual macro command you want count to do over and over again.
Let's look at an example. Suppose you have a character who can cast a spell, which creates a cloud of poisonous gas that can hit up to nine targets at the same time. Suppose we also have a cluster of 6 hapless orcs standing in the room, that you are about to poison with this toxic cloud. The rules of your game indicate that you must roll a separate attack for each possible target, meaning that you'd have to roll your attack 6 different times. You can either do that by hand, each time, or you could write a macro that uses COUNT():
to roll the attack over and over, and all you need to give it is the number of times!
Here's how you'd write that macro:
[h:attackBonus = 7] Toxic Cloud: [count(numAttacks): 1d20+attackBonus]
What we did here was:
- Line 1 sets a value for
attackBonus
, to be used later. - Line 3 sends the text "Toxic Cloud: " to chat, and then, begins the Count Loop. Since
numAttacks
is undeclared, MapTool will prompt you for a value before it can start the loop. Once you enter that value, the count loop will process the calculation of1d20+attackBonus
that many times, sending the result to chat each time, and separating each result with a comma.
The output of this macro will look something like (assuming you entered 4 when prompted for numAttacks
):
Toxic Cloud: 17, 19, 12, 8
Special Variable: roll.count
Since it's often useful to know what "round" or "turn" we're on when a COUNT():
loop is running, MapTool creates a special variable every time you start a count loop. This variable is called roll.count
, and it's value is equal to whatever loop you're currently on. So, if you're on the first loop, roll.count
is equal to 1; on the second time through, it's equal to 2, and so on. That way, you can use that value in various ways inside your macro command.
A more advanced example of the Toxic Cloud macro might look like this:
[h:attackBonus = 7] Toxic Cloud vs:<br> [c(numAttacks, "<br>"),CODE: { [attack = 1d20+attackBonus] [damage = 1d6 + 2] Target [r:roll.count]: Attack [r:attack]; [damage] damage. }]
A bit more is going on here.
- Line 1 still just sets
attackBonus
to 7, so we can use it later. - Line 3 outputs "Toxic Cloud vs:
" to chat (the <br> part sends a line break to chat, meaning that the next output will start on the line below the words "Toxic Cloud vs:") - Line 4 starts a more complex Count Loop. First off, we used the abbreviation for "count", which is just
c
. We leftnumAttacks
as is, but added a second argument - in this case, a different "separator." All loops in MapTool macros have a default separator, which is the comma. However, you don't always want to separate your results with a comma, right? In this situation, we want to separate them with a line break, so each result is on its own line in chat. So, we put the new separator in - an HTML line break character, or <br>. - Line 4 also uses the CODE roll option, which is discussed in Introduction to Macro Branching, and lets us do multiple operations as a single group.
- Line 6 sets the variable
attack
to the sum ofattackBonus
and 1d20. - Line 7 sets the variable
damage
to the sum of1d6+2
. - Line 8 is a combination of text and variables, which are output to chat. Note that the variable
roll.count
is in there, which will be replaced with whatever iteration the loop happens to be on. - Line 9 closes the CODE block, and the whole command.
The output from this would look something like:
Toxic Cloud vs:
Target 1: Attack 17; 6 damage.
Target 2: Attack 12; 5 damage.
Target 3: Attack 19; 7 damage.
Target 4: Attack 10; 3 damage.
WHILE: Keep On Keepin' On
Let's move on to a new looping structure: [WHILE():]
. This structure is the first one we'll discuss that uses a condition to determine how many times to loop (previously, count used a value - but not a comparison of any kind). The general format of a [WHILE():]
loop is:
[while(condition): command]
You're probably getting used to reading these by now.
- WHILE(): of course, we need to add the roll option itself
- condition: this is the comparison that we make, to see if the loop needs to stop - it can be any of the logical comparisons we've discussed already (such as,
loops < 10
ornumDice > 5
or anything you can think of). - command: the macro command (or commands, if you use the CODE option) to run each time the loop goes 'round.
So, if you read this, the while loop really just says, "while some condition is true, keep doing this." Let's look at some examples.
Example 1: Basic Countdown
This is a very basic example, just to illustrate the basic parts of the WHILE() loop. Suppose you wanted to count down from 10 to 1. There are many ways to do this, of course, but we'll do it with a WHILE() loop. The macro would look like this:
[h:num = 10] [while(num > 0): num = num - 1]
The above macro simply says that "while the variable num
is greater than 0, subtract 1 from num
, and repeat." Each time through the loop, the value of num
will be checked by MapTool. If it is greater than 0, it will let the command num = num-1
happen, and display the result in chat; if num
is not greater than 0, then the loop will be halted. The output of this macro looks like:
9,8,7,6,5,4,3,2,1
You'll note that "10" was never shown. That's because while the loop may have started with num
having the value of 10, the first time we see any output is when the operation num -1
takes place - so the first thing we see is num - 1
, which is 9.
Example 2: The Machine-Gun
Let's look at a more complicated (and perhaps more interesting) example. In this example, we won't be using the Sample Ruleset, mostly because I couldn't think of a useful example from that game. So, let's assume we have the following game situation:
- A character has a machine gun with 30 rounds of ammunition
- They may fire one to six rounds for every action
- If their attack roll of 1d20 is greater than 15, they may make another attack; otherwise, they must end their turn. They always get at least 1 attack, though.
So what we need to do is repeat the operation until either the weapon runs out of ammo, or they roll less than a 15 on their attack. Here's how that macro would look:
[h:ammo = 30] [h:hit = 1] [while(ammo > 0 && hit == 0),CODE: { [h:attackRoll = 1d20] [h:ammoSpent = 1d6] [h,if(attackRoll > 15): hit = 1; hit = 0] [h:ammo = ammo - ammoSpent] Your first attack expends [r:ammoSpent] rounds, and [if(hit==1, "hits.", "misses.")] You have [r:ammo] rounds remaining. }] You have run out of ammo, or missed your target. Your turn is over.
Here's the breakdown of this macro:
- Line 1 and 2 set two important variables:
ammo
andhit
. We setammo
to 30, per the assumptions above, and we sethit
to 1, so that the character always gets at least one attack roll (if we didn't sethit
to 1, the loop might stop before it started!). - Line 4 is the start of the While Loop: we establish the loop, and give it a combined condition. We say that while
ammo
has a value greater than 30, and (remember, two ampersands is the logical operator "and")hit
is equal to 1, the loop should go 'round. If either or both of those is not true, then the loop should stop. - Lines 5 - 9 handle the actual loop processing. Note that in that loop, we make sure to set a new value for
hit
andammo
- this is critical. If you never change the variables that your conditions are based on, then your loop will never stop.
It's worth repeating: in a while loop, you must change the variable that your condition is checking, or the loop will never stop.