MapTool – Stupidly complex multi-attack macro

I haven’t posted any MapTool macros in a while, but I just finished writing one that ended up being so stupidly complex that I just had to share it with the world.

First a disclaimer: Yes, I am aware that there are wonderful MapTool frameworks out there that will help automate everything and make it so that I barely need to touch any macros.  I write my own macros just for the fun of it because I’m nerdy like that.  I share them here on my blog because I know there are some other nerdy people out there who might be able to use some of my learnings in their own macros.

Okay, here’s the situation.  There’s a tiefling wizard coming up in a battle for my Friday night War of the Burning Sky campaign.  It’s not a big boss or anything like that, just an ordinary bad guy.  This creature has several different attack powers, as you might expect with a wizard.  The one particular attack power that I’m about to show you is called Dancing Lightning

  • Dancing Lightning involves three attacks (separate attack rolls, separate damage rolls) against three different creatures.
  • This is a recharge power, recharging on a 6.
  • As a tiefling, the creature gets a +1 bonus to attack rolls against bloodied targets.
  • The wizard has a magic staff with a daily power that she can use whenever she uses Dancing Lightning.  If she uses the daily, she deals some bonus damage to creatures in a close blast 3 (which might be some of the same creatures targeted by the attack, or it might not).

Now, I could handle this with separate macros to track just the recharging or just the daily power, and I could decide not to worry about the +1 to attack rolls against bloodied targets.  But instead I decided to go all-in and make this one macro handle everything.

First, the recharge bit:

[h: RechargeTarget=6]
[h: RechargeRoll=d6]
[if(R1!=1), CODE:
 {[if(RechargeRoll<RechargeTarget), CODE:
 {[g: assert(1==0,add("Recharge roll = ", RechargeRoll, ". The power fails to recharge."),0)]
 };{}]
 };{[setProperty("R1",0)]}
]

This is my standard recharge code.  It sets the target number for recharging at 6.  It rolls a d6 and stores the result as RechargeRoll.  It then checks a property on the token called R1 (which is equal to 1 when the battle begins and is set to zero after the power has been used).  If R1 is not equal to 1 (that is, it’s zero because the power has already been used at least once), then the macro checks to see if the recharge roll was at least 6.  If not, it uses the ASSERT function to give an error message (no recharge) and the macro ends.  If the recharge roll is 6, then the macro moves on.  Finally, if R1 was equal to 1 (that is, if the power hadn’t been used yet this battle), the macro sets it to zero so that it won’t work next time unless it recharges.

Next, the standard attack macro setup stuff:

[h: AttackName="Dancing Lightning"]
[h: AttackBonus=14]
[h: Defense="Reflex"]
[h: NumDice=2]
[h: DamageDie=6]
[h: DamageBonus=5]
[h: DamageString="lightning damage."]
<b>[AttackName]</b><br>

Here I set up the name of the attack to be displayed, the attack bonus, the defense that is being attacked, the number of damage dice I’m going to roll, the size of the damage die, the number I’m adding to the damage dice and the text that I want to display after the damage number (in this case, “lightning damage”).  The last line displays the name of the attack in the chat window in bold type and then inserts a line break.  Simple stuff.  I only bother using these variables because for most attacks I just set these things at the top of my code and then I’m done.

Now we get into the stuff for the first actual attack and damage roll:

[h: x=input("FoeBloody|No,Yes|Is the first target bloodied?|RADIO|VALUE=STRING")]
[h: abort(x)]
[h: AttackBonus=if(FoeBloody=="Yes",AttackBonus+1,AttackBonus)]
[h: DamageRoll=roll(NumDice,DamageDie)]
[h: d20roll=d20]
[h: AttackRoll=d20roll+AttackBonus]
[h: MaxDamage=NumDice*DamageDie+DamageBonus]
[h: RegularDamage=DamageRoll+DamageBonus]

The first line above will result in me getting a pop-up dialog box that asks if the first target is bloodied.  The second line will end the macro if I click “Cancel” in that pop-up.  The third line checks to see if I said the first target was bloodied; if so, it adds 1 to the attack bonus (the tiefling ability).After that, the macro does a damage roll (2d6 in this case). It does a d20 roll for the attack, adding the attack bonus and calling it AttackRoll.  It calculates the maximum possible damage (in case of a crit) as well as the regular damage (from the damage roll plus the bonus).  Again, pretty simple.

Next, I display the results of the first attack in the chat window:

Attack 1: [d20roll] + [AttackBonus] = <b>[AttackRoll]</b> versus [Defense]<br>
[if(d20roll==20), CODE:
 {<font color=Red>--CRITICAL HIT--</font><br>
 Hit 1: [NumDice*DamageDie] + [DamageBonus] = <b>[MaxDamage]</b> [DamageString]
 };
 {Hit 1: [DamageRoll] + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]}
]

The first line shows something like: “Attack 1: 7 + 14 = 21 versus Reflex”  Then I check to see if there was a critical hit.  If so, I display the maximum damage (along with a crit message); otherwise, I show the damage that was rolled for this attack.  Again, standard stuff from my regular attack macros.

After this, I repeat those last two sections (starting with the FoeBloody piece) for the second and third attacks (changing the language to “second target,” “Attack 2,” “Hit 2,” and so on).  I put a couple of line breaks in between as well.

Finally, there’s the piece of code to deal with whether I want to use the bonus daily damage in a close blast 3 or not.  Generally I’ll just use it at the first opportunity, of course – this is a recharge 6 power, which means it’s highly unlikely that I’ll get a second chance to use it.  But hey, for the sake of completeness, I wanted the option to be built in to the macro.

[if(E2==1), CODE:
 {[h: x=input("UseDaily|Yes,No|Use the daily close blast 3 power now?|RADIO|VALUE=STRING")]
 [h: abort(x)]
 [if(UseDaily=="Yes"),CODE:
 {[h: E2=0]
 <br><br><i>Quarterstaff of Storms</i>: Each enemy in a close blast 3 takes an additional [d8] lightning and thunder damage.
 }; {}
 ]
 };
 {}
]

This one is very messy to look at, mainly because it uses nested IF statements (an IF within an IF).  It first looks at a property of the wizard token called E2 (this is for her second encounter power – the first is Infernal Wrath).  If E2 equals one, that means that I haven’t already used this daily yet, in which case the code moves on to ask me if I want to use it this time.  It does so with another pop-up dialog box:

Now, if I do choose to use the daily power, the macro sets E2 equal to zero (to show that the power has been used up).  It then inserts two more line breaks, displays the name of the daily power in italics and then says what happens to the enemies in the blast (including the damage roll).  Note that if E2 were equal to zero (meaning that the daily item power had already been used), then the rest of the code is skipped over.

When I run the power, the output in the chat window looks like this:

And the finished macro in all its glory is as follows:

[h: RechargeTarget=6]
[h: RechargeRoll=d6]
[if(R1!=1), CODE:
 {[if(RechargeRoll<RechargeTarget), CODE:
 {[g: assert(1==0,add("Recharge roll = ", RechargeRoll, ". The power fails to recharge."),0)]
 };{}]
 };{[setProperty("R1",0)]}
]

[h: AttackName="Dancing Lightning"]
[h: AttackBonus=14]
[h: Defense="Reflex"]
[h: NumDice=2]
[h: DamageDie=6]
[h: DamageBonus=5]
[h: DamageString="lightning damage."]

<b>[AttackName]</b><br>

[h: x=input("FoeBloody|No,Yes|Is the first target bloodied?|RADIO|VALUE=STRING")]
[h: abort(x)]
[h: AttackBonus=if(FoeBloody=="Yes",AttackBonus+1,AttackBonus)]
[h: DamageRoll=roll(NumDice,DamageDie)]
[h: d20roll=d20]
[h: AttackRoll=d20roll+AttackBonus]
[h: MaxDamage=NumDice*DamageDie+DamageBonus]
[h: RegularDamage=DamageRoll+DamageBonus]

Attack 1: [d20roll] + [AttackBonus] = <b>[AttackRoll]</b> versus [Defense]<br>
[if(d20roll==20), CODE:
 {<font color=Red>--CRITICAL HIT--</font><br>
 Hit 1: [NumDice*DamageDie] + [DamageBonus] = <b>[MaxDamage]</b> [DamageString]
 };
 {Hit 1: [DamageRoll] + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]}
]

[h: "Second Attack"]
[h: x=input("FoeBloody|No,Yes|Is the second target bloodied?|RADIO|VALUE=STRING")]
[h: abort(x)]
[h: AttackBonus=if(FoeBloody=="Yes",AttackBonus+1,AttackBonus)]
[h: DamageRoll=roll(NumDice,DamageDie)]
[h: d20roll=d20]
[h: AttackRoll=d20roll+AttackBonus]
[h: MaxDamage=NumDice*DamageDie+DamageBonus]
[h: RegularDamage=DamageRoll+DamageBonus]
<br><br>
Attack 2: [d20roll] + [AttackBonus] = <b>[AttackRoll]</b> versus [Defense]<br>
[if(d20roll==20), CODE:
 {<font color=Red>--CRITICAL HIT--</font><br>
 Hit 2: [NumDice*DamageDie] + [DamageBonus] = <b>[MaxDamage]</b> [DamageString]
 };
 {Hit 2: [DamageRoll] + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]}
]


[h: "Third Attack"]
[h: x=input("FoeBloody|No,Yes|Is the third target bloodied?|RADIO|VALUE=STRING")]
[h: abort(x)]
[h: AttackBonus=if(FoeBloody=="Yes",AttackBonus+1,AttackBonus)]
[h: DamageRoll=roll(NumDice,DamageDie)]
[h: d20roll=d20]
[h: AttackRoll=d20roll+AttackBonus]
[h: MaxDamage=NumDice*DamageDie+DamageBonus]
[h: RegularDamage=DamageRoll+DamageBonus]
<br><br>
Attack 3: [d20roll] + [AttackBonus] = <b>[AttackRoll]</b> versus [Defense]<br>
[if(d20roll==20), CODE:
 {<font color=Red>--CRITICAL HIT--</font><br>
 Hit 3: [NumDice*DamageDie] + [DamageBonus] = <b>[MaxDamage]</b> [DamageString]
 };
 {Hit 3: [DamageRoll] + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]}
]

[if(E2==1), CODE:
 {[h: x=input("UseDaily|Yes,No|Use the daily close blast 3 power now?|RADIO|VALUE=STRING")]
 [h: abort(x)]
 [if(UseDaily=="Yes"),CODE:
 {[h: E2=0]
 <br><br><i>Quarterstaff of Storms</i>: Each enemy in a close blast 3 takes an additional [d8] lightning and thunder damage.
 }; {}
 ]
 };
 {}
]

Leave a Reply