macros:HowTo:howto
Macro How To
How to automate updating a token property
This section expects that you are already familiar with how to add macro buttons to the MapTool user interface.
Example: Updating Hit Points
Let's say you have a property to represent hit points. We'll call our property HP. Now we want some easy way to update HP, so we're going to create a macro button that executes a macro.
First, consider how you want this to work. We want a window to popup on the screen and ask the user to enter a number. That number will be subtracted from HP, so the user can use a positive number to represent damage and a negative number to represent healing. (We'll show another approach later.)
The first step will be to prompt for the number. MapTool has this ability built-in. All we need to do is use a variable name that doesn't exist yet and MapTool will popup the prompt! The name of the variable is part of the prompt, so we'll use a descriptive name. How about AmountOfDamage?
[ damage = AmountOfDamage ]
Notice the extra variable name and the equals sign? That tells MapTool to calculate whatever is on the right of the equals sign and store the result into the variable on the left. In this case, there's no formula, so this becomes simply a copy -- from the variable AmountOfDamage to damage. But when MapTool tries to read the value of the variable and it doesn't exist, the popup will automatically appear! That's perfect for what we want!
Now the next step is to subtract that from the HP property. Fortunately, what you learned in the last paragraph can be used again:
[ damage = AmountOfDamage ]
[ HP = HP - damage ]
This time the second line calculates the formula on the right (HP - damage) and stores the result into ... HP? Isn't that going to screw up our HP value?
No, it doesn't screw it up, but it does replace that value with the result. And because HP is a property, the result is stored back into the token's property. If you were to right-click on the token and save it to an external file, the new value of HP is stored with it. When the token is later reloaded, that value will come with it.
If you want to add to the hit points instead, you have two choices: either the user can enter a negative number, or you can change the - to a +. The first option is easy because it puts the burden on the user! The second option is really an option -- who wants to edit their macro every time they want to switch from damage to healing? Another choice not listed above would be to create a second macro. Then there could be one macro for adding damage and one for adding healing.
There's a few things still needed here to make this a little prettier, but those are future steps. Go ahead and try this out right now on a token that you create in MapTool. (The default property type, Basic, includes a property named HP.) And try adding the second macro as well, just for the practice. (Believe me, the more practice you get early in the process, the easier it will become later on.)
Example: Let's Rest for a Minute...
So let's say that you now have a macro button that prompts you to change the token's hit points through damage or healing as described above. How do we reset their hit points to their maximum when they rest?
We already know that we have a HP and HPmax properties, so when they are healed up we simply need to copy the value in HPmax into HP. That should give you what you need to create a simple one-line macro:
[ HP = HPmax ]
Simple, right? But for the sake of argument, let's expand on this a bit. Instead of restoring all of the hit points to the creature, we will prompt the user for the number of hours that the creature will be resting. For my demonstration, I'm assuming that there's a property named Level. If it rests for less than 24 hours, it gets back Level*2 hit points. If it rests for 24 hours or more, it gets back Level*6.
[ hours = NumberOfHours ]
[ healing = if(hours < 24, Level * 2, Level * 6) ]
[ HP = HP + healing ]
You may notice the if() function on the second line. One word of warning when using the if() function: both the true and the false sections are executed! For that reason, you may want the IF() option instead. Note that the syntax is slightly different between the two, so be careful about which one you choose.
Example: One Macro to Rule Them All
((macro that uses input() to prompt for values with radiobuttons))
How to manipulate a JSON property
Example: jsonFilterArrObj() -- Filtering out objects from an array of objects
If you have an array of objects and want to filter that list given one of the elements in the object, this subroutine/callable macro jsonFilterArrObj will do the job.
As an example, here is an array of creature data:
JSON array of Creature objects
[{
"name": "Umber Hulk",
"hd": 8,
"size": "Large",
"reach": 10,
"str": 23,
"dex": 13,
"con": 19,
"mov": "20, burrow 20",
"AC": "18/10/17",
"ArmorClass": "Armor=0 ; Shield=0 ; ArmorACP=0 ; ShieldACP=0 ; MaxDex=50 ; Natural=8 ; Deflection=0 ; Dodge=0 ; Description= ;",
"SpecialATK": "Confusing Gaze(Su)"
},
{
"name": "Ogre",
"hd": 4,
"size": "Large",
"reach": 10,
"str": 21,
"dex": 8,
"con": 15,
"mov": 30,
"AC": "16/8/17 Hide",
"ArmorClass": "Armor=3 ; Shield=0 ; ArmorACP=0 ; ShieldACP=0 ; MaxDex=50 ; Natural=5 ; Deflection=0 ; Dodge=0 ; Description=Hide ;",
"SpecialATK": "NA"
}]
If you want to shorten the list by filtering on various elements of the object, you can call the jsonFilterArrObj macro to return the filtered array of objects.
To call the macro, setup the JSON parameter to pass like thus:
Calling Macro (Remove all Creatures that have more HitDice than the entered number)
<!--
"hd" = Element I want to filter against for this example
jAll = JSON array of Creature objects
fHD = number I prompted for with an input() dialog, this is the value I am testing against
-->
[H: jAll = "[]"]
[H, FOR(i,1,cntAll): jAll = json.append(jALL, json.set(table("Polymorph", i), "imageID", tableimage("Polymorph", i)))]
[H: jAll = json.sort(jAll, "a")]
[H: tjF = json.append("[]", jAll)]
[H: tjF = json.append(tjF, "hd")]
[H: tjF = json.append(tjF, fHD)]
[H: tjF = json.append(tjF, "<")]
[H, MACRO("jsonFilterArrObj@"+getMacroLocation()): tjF]
[H: jFiltered = macro.return]
[H: Assert(!(json.isEmpty(jFiltered)), "Polymorph: No choices available.", 0)]
[H: cntF = json.length(jFiltered)]
<!-- continue processing with the newly filtered array of objects -->
jsonFilterArrObj
<!--
Filter out objects from an array of objects.
inputs (macro.args is a JSON array containing):
jAO = A JSON array of objects
elem = The element to filter against
fDat = The data to compare against
fType = Filter comparison: >, <, ==, >=, <=, !=
output:
macro.return = jAOf (The array with the elements removed that met the criteria)
-->
[H: jAO = json.get(macro.args, 0)]
[H: elem = json.get(macro.args, 1)]
[H: fDat = json.get(macro.args, 2)]
[H: fType = json.get(macro.args, 3)]
[H: cntAll = json.length(jAO)]
[H: assert(cntAll, "Null Array so filter aborts.")]
[H, for(i, cntAll - 1, 0, -1), CODE: {
[H: tDat = json.get(json.get(jAO, i), elem)]
[ bTest = eval("fDat"+fType+"tDat")]<br>
[H, IF(bTest): jAO = json.remove(jAO, i); ""]
}]
[H: macro.return = jAO]