MapTool – Creating monster power macros with a macro

Strap in, macro lovers – you’re in for quite a ride!

As I mentioned in my last post, I’ve wanted for a while to create monster power macros using another macro. I’d like to be able to click an “Add Power” button, specify the details of the attack, and see a new button for that attack get created. And now, at long last, I’ve done exactly that.

It’s not a simple macro, to be sure. But I think it’s quite slick. Read on, if you dare! And note that you can download the macro here. My current D&D 4e campaign framework is available here.

First, some new properties

As I was going through this process, I realized that I would need to update my monster properties to better handle the tracking of encounter and recharge powers. I previously handled this with properties E1 through E7, which begin with a value of 1 and then get set to 0 once the encounter power is used, after which it can’t be used again. I did similarly with R1 through R5 for recharge powers.

Since my new macro is going to be creating encounter powers, it needs to know which encounter power slots are already taken. The cleanest way to do this was with pairs of JSON arrays as properties.

EncounterPowersCharged:'[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]’
EncounterPowersDeclared:'[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]’
RechargePowersCharged:'[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]’
RechargePowersDeclared:'[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]’

These are ten-item arrays, allowing for ten separate encounter powers and ten separate recharge powers. When I assign a new encounter power to one of the slots, I change the appropriate slot in the EncounterPowersDeclared array to 1. When that power is used up, it changes the appropriate slot in the EncounterPowersCharged array to 0, preventing it from being used again. And so on for recharge powers.

More on this later.

Macro structure

This macro has three main sections.

– There’s the first input box, where I ask for the name of the power, what range it targets, whether it’s standard or minor or whatever, what order I want the power to appear in, the usage (at-will, encounter, recharge), how many targets it has, whether it requires an attack roll and/or damage roll, and whether there are various other lines of text in the power (a Requirement line, a miss effect, etc.).

– There’s a second input box, where I ask about extra bonuses to the attack roll, what level of damage is being dealt, and what should appear in the various lines of text of the power.

– There’s the actual construction of the macro command text, which gets rather complicated.

First input box

I’ll present the code as a big dump, then explain it.

[h: listActionTypes = “2. Traits, 3. Standard Actions, 4. Move Actions, 5. Minor Actions, 6. Triggered Actions”]
[h: listDamageLevel = “Minion, Very Low, Low, Normal, High, Very High, Extreme”]
[h: listUsage = “At-Will, Recharge, Encounter, Not Applicable”]
[h: x = input(
“MacroName | NewPower| Power Name || WIDTH=20”,
“TargetRange | (M1) | Targets-Aura-Etc || WIDTH=10”,
“ActionType | ” + listActionTypes + ” | Action Type | LIST | VALUE=STRING SELECT=1″,
“SortOrder | 1 | Sort order || WIDTH=2”,
“Usage | ” + listUsage + ” | Usage | LIST | VALUE=STRING SELECT=0″,
“NumberOfTargets | Single-Target, Multi-Target, No Targets | Number of Targets | LIST | VALUE=STRING SELECT=0”,
“AttackRollRequired | 1 | Attack roll? | CHECK | “,
“DamageRollRequired | 1 | Damage roll? | CHECK | “,
“RequirementIncluded | 0 | Requirement line? | CHECK | “,
“PreAttackEffectIncluded | 0 | Pre-attack effect? | CHECK | “,
“MissEffectIncluded | 0 | Miss effect? | CHECK | “,
“AftereffectIncluded | 0 | After-save effect? | CHECK | “,
“PostAttackEffectIncluded | 0 | Post-attack effect? | CHECK | “,
“SpecialEffectIncluded | 0 | Special effect? | CHECK | ”
)]
[h: abort(x)]

The first three lines just set up some string lists that the user will be able to choose from for action type, damage level and usage. Then I have a big input box where I ask the user to make some choices. It’s probably easiest to just look at it in action:

The user is invited to type in the name of the power and any information about its range (such as M1 for melee 1 or AB1 in 10 for area burst 1 within 10 squares). A drop-down lets you pick whether it’s standard, minor, etc. The sort order only matters if you have multiple powers of the same action type – which standard action do you want first, then second, etc. The targets are either Single-Target, Multi-Target or No Targets. Then we have a series of check boxes that ask whether different possible parts of a power are present in this power. Note that the default options assume an attack roll and a damage roll and nothing else, but that’s all easy to edit with a click.

Second input box

[h: DefenseTargeted=’AC’]
[h: Enhancement=0]
[h: DamageLevel=”Normal”]
[h: DefaultDamageLevel=3]

Here I set up a few default values. These are useful in case I’ve said in the first input box that the power has no attack roll, in which case I won’t be picking a defense to target or a possible enhancement for critical rolls. That can be a problem later in the macro, so I need some defaults.

The DefaultDamageLevel is a variable that determines where the starting position in the damage drop-down will be. Position 3 is “Normal”. But in certain cases I want to default to things other than “Normal”:

[h, if(getProperty(“Role”)==”Brute”): DefaultDamageLevel=DefaultDamageLevel+1]
[h, if(Usage==”Recharge”): DefaultDamageLevel=DefaultDamageLevel+1]
[h, if(Usage==”Encounter”): DefaultDamageLevel=DefaultDamageLevel+2]
[h, if(NumberOfTargets==”Multi-Target”): DefaultDamageLevel=DefaultDamageLevel-1]
[h, if(getProperty(“SubType”)==”Minion”): DefaultDamageLevel=0]

Brutes deal more damage than other creatures. Recharge powers should do a bit more damage than normal, other things being equal. Encounter powers should do more still. Now, if the attack hits multiple targets, that should bring the damage down a bit. And of course if we’re building a power for a minion, we should use fixed minion damage.

Okay, now things start getting complicated.

[h: RechargeTargetInputString=if(Usage==”Recharge”,encode(“RechargeTarget | 2, 3, 4, 5, 6 | Recharge Target | LIST | VALUE=STRING SELECT=3″),””)]

What’s going on here? Well, I’m getting ready to build another input box. I want to be slick with this one, so it’s only going to ask the user for information that’s relevant. This means that if I’m building a recharge power, I want to ask for a recharge target number, but if I’m building an at-will or encounter power I don’t want to bother asking for a recharge number.

So, I create a string that will show up in the next input() command for the recharge target line. If I am building a recharge power, the string will be encoded to ask for a variable called RechargeTarget, which could range from 2 to 6. It will ask the user for a Recharge Target value in a drop-down list and save the choice as a string. The default selected in the drop-down will be at index 3 of the list which, because MapTool starts counting at 0, is actually the fourth item – which is the number 5, the most common recharge target.

However, if I’m NOT building a recharge power (Usage is anything other than Recharge) then instead of encoding this big string, I just set RechargeTargetInputString equal to “” – a blank string.

I then do similar things for lots of other possible inputs.

[h: DamageRollInputString=if(DamageRollRequired==1,encode(“DamageLevel | ” + listDamageLevel + ” | Damage Level | LIST | VALUE=STRING SELECT=” + DefaultDamageLevel),””)]
[h: EnhancementInputString=if(DamageRollRequired==1,encode(“Enhancement | 0 | Extra damage on crit? | CHECK | “),””)]
[h: ExtraAttackBonusInputString=if(AttackRollRequired==1,encode(“ExtraAttackBonus | 0 | Extra attack bonus | | WIDTH=2″),””)]
[h: JustEffectInputString=if(AttackRollRequired==0,encode(“JustEffect | . | Effect line | | WIDTH=50″),””)]
[h: DefenseInputString=if(AttackRollRequired==1,encode(“DefenseTargeted | AC, Fortitude, Reflex, Will | Defense targeted | LIST | VALUE=STRING SELECT=0″),””)]
[h: HitStringInputString=if(AttackRollRequired==1&&DamageRollRequired==1,encode(“HitStringWithDamage | damage | Hit line after damage roll | | WIDTH=50”),if(AttackRollRequired==1&&DamageRollRequired==0,encode(“HitStringNoDamage | . | Hit line | | WIDTH=50”),if(AttackRollRequired==0&&DamageRollRequired==1,encode(“EffectStringNoAttack | damage | Effect line after damage roll | | WIDTH=50″),””)))]
[h: RequirementInputString=if(RequirementIncluded==1,encode(“Requirement | . | Requirement | | WIDTH=50″),””)]
[h: TriggerInputString=if(ActionType==”6. Triggered Actions”,encode(“Trigger | . | Trigger | | WIDTH=50″),””)]
[h: PreAttackEffectInputString=if(PreAttackEffectIncluded==1,encode(“PreAttackEffect | . | Pre-Attack Effect | | WIDTH=50″),””)]
[h: MissEffectInputString=if(MissEffectIncluded==1,encode(“MissEffect | . | Miss Effect | | WIDTH=50″),””)]
[h: AftereffectInputString=if(AftereffectIncluded==1,encode(“Aftereffect | . | Aftereffect | | WIDTH=50″),””)]
[h: PostAttackEffectInputString=if(PostAttackEffectIncluded==1,encode(“PostAttackEffect | . | Post-attack effect | | WIDTH=50″),””)]
[h: SpecialEffectInputString=if(SpecialEffectIncluded==1,encode(“SpecialEffect | . | Special Effect | | WIDTH=50″),””)]

All of these things could be suppressed if the power in question doesn’t need them. I’ve tried to present them as intuitively as possible. I only ask for the damage level if there’s a damage roll (and the default selection was set up above). I ask if this power should have a little extra on the attack roll (like for a ranged artillery attack). If this power is just an effect (no attack, no damage – like a trait), then I ask for that. And so on.

Now it’s time to actually generate the input box that will ask for this stuff:

[h: x = input(
decode(RechargeTargetInputString),
decode(RequirementInputString),
decode(TriggerInputString),
decode(PreAttackEffectInputString),
decode(ExtraAttackBonusInputString),
decode(DefenseInputString),
decode(DamageRollInputString),
decode(EnhancementInputString),
decode(HitStringInputString),
decode(MissEffectInputString),
decode(AftereffectInputString),
decode(PostAttackEffectInputString),
decode(SpecialEffectInputString),
decode(JustEffectInputString)
)]
[h: abort(x)]

Yep – it’s all just a bunch of decoding of the stuff above. If the conditions are right, there will be an item in the input box. If not, the item will be skipped. For instance:

This is what I get if I pick a triggered recharge power with all of the check boxes checked. Conversely:

This is what I get if I choose the default options (single target at-will power with an attack and damage roll). And for the extreme simplicity case:

This is what I get if I pick a power with no targets, no attack roll, no damage roll and no boxes checked. It’s a pure text power – just an effect. Most move actions and traits will be this way, for instance.

Building the macro command

At this point, we have a lot of input from the user saved as variables. Now it’s time to use those variables and turn it all into command text for our new macro.

[h, switch(DamageLevel), code:
case “Minion”: {
[h: NumDice=0]
[h: DieSize=”MinionDamage”]
[h: DamageBonus=”MinionDamage”]
[h: Enh=1]
[h: CritDamageDie=”MinionDamage”]
};
case “Very Low”: {
[h: NumDice=”NumDice50″]
[h: DieSize=”DamageDie50″]
[h: DamageBonus=”DamageBonus50″]
[h: Enh=Enhancement]
[h: CritDamageDie=”DamageDie50″]
};
case “Low”: {
[h: NumDice=”NumDice75″]
[h: DieSize=”DamageDie75″]
[h: DamageBonus=”DamageBonus75″]
[h: Enh=Enhancement]
[h: CritDamageDie=”DamageDie75″]
};
case “Normal”: {
[h: NumDice=”NumDice100″]
[h: DieSize=”DamageDie100″]
[h: DamageBonus=”DamageBonus100″]
[h: Enh=Enhancement]
[h: CritDamageDie=”DamageDie100″]
};
case “High”: {
[h: NumDice=”NumDice125″]
[h: DieSize=”DamageDie125″]
[h: DamageBonus=”DamageBonus125″]
[h: Enh=Enhancement]
[h: CritDamageDie=”DamageDie125″]
};
case “Very High”: {
[h: NumDice=”NumDice150″]
[h: DieSize=”DamageDie150″]
[h: DamageBonus=”DamageBonus150″]
[h: Enh=Enhancement*2]
[h: CritDamageDie=”DamageDie150″]
};
case “Extreme”: {
[h: NumDice=”NumDice175″]
[h: DieSize=”DamageDie175″]
[h: DamageBonus=”DamageBonus175″]
[h: Enh=Enhancement*2]
[h: CritDamageDie=”DamageDie175″]
}
]

This rather inelegant piece of code does something simple in the end. It takes the indication of the damage level (from Minion to Very Low all the way up to Extreme) and sets some text variables that will show up in the final macro. I have properties in my campaign for number of dice, damage die size and damage bonus that are derived from a monster’s level. I also have tweaks from the 100% “normal” damage expressions to have 50% versions, 75%, 125%, 150% and 175%. That’s all I’m doing here – getting ready to tell the final macro what damage level to pull from the monster’s properties.

[h: AttackBonus=if(DefenseTargeted==’AC’,’DefaultAttackBonusVsAC’,’DefaultAttackBonusVsNAD’)]

This is a simple one. I have two monster properties for attack bonuses: One versus AC and one versus non-AC defenses (which is two points lower). I’m just telling the macro which one to use.

[h: MyCommand=””]
[h: MyCommand=MyCommand+encode(“<b>” + MacroName + “</b> ” + TargetRange + “<br>\\n”)]

As you saw in my last post, this is the way I build the “command” property that I’m setting for the new macro. I’m just adding the name and range at this point. The \\n at the end enters a new line (carriage return) in the macro itself, not in the output.

[h, if(RequirementIncluded==1): MyCommand=MyCommand+encode(“<i>Requirement:</i> “+Requirement+”<br>\\n”)]
[h, if(ActionType==”6. Triggered Actions”): MyCommand=MyCommand+encode(“<i>Trigger:</i> “+Trigger+”<br>\\n”)]
[h, if(PreAttackEffectIncluded==1): MyCommand=MyCommand+encode(“<i>Effect:</i> “+PreAttackEffect+”<br>\\n”)]

Here I add lines for Requirement, Trigger and Effect before the attack in the case that those exist for this power.

[h, if(AttackRollRequired==1), CODE: {
[h: MyCommand=MyCommand+encode(“[h: AttackBonus=” + AttackBonus + ” + ” + ExtraAttackBonus + “]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: Defense='”+DefenseTargeted+”‘]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: d20roll=d20]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: Enh=”+Enh+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: CritDamageDie=”+CritDamageDie+”]\\n\\n”)]
[h: MyCommand=MyCommand+encode(“[h, if(CritDamageDie > 0), CODE:\\n { [CritBonus=roll(Enh,CritDamageDie)] }; \\n { [CritBonus=0] }\\n ]\\n\\n”)]
[h: MyCommand=MyCommand+encode(“[h: AttackRoll=d20roll+AttackBonus]\\n”)]
}
]

These lines are only added to the macro if there’s an attack roll. The macro will have the attack bonus, the defense targeted, the result of a d20 roll, etc.

[h, if(DamageRollRequired==1), CODE: {
[h: MyCommand=MyCommand+encode(“[h: DamageString='”+HitStringWithDamage+”‘]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: NumDice=”+NumDice+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: DamageDie=”+DieSize+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: DamageBonus=”+DamageBonus+”]\\n\\n”)]
[h: MyCommand=MyCommand+encode(“[h: DamageRoll=roll(NumDice,DamageDie)]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: NumDice=”+NumDice+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: MaxDamage=NumDice*DamageDie+DamageBonus+CritBonus]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: RegularDamage=DamageRoll+DamageBonus]\\n\\n”)]
}
]

These lines only show up if there’s a damage roll. Pretty standard stuff.

[h, if(NumberOfTargets==”Single-Target” && AttackRollRequired==1), CODE: {
[h: MyCommand=MyCommand+encode(“<i>Attack:</i> [d20roll] + [AttackBonus] = <b>[AttackRoll]</b> versus [Defense]<br>\\n”)]
[h, if(DamageRollRequired==1), CODE: {
[h: MyCommand=MyCommand+encode(“[if(d20roll==20), CODE:\\n”)]
[h: MyCommand=MyCommand+encode(” {<font color=red>–CRITICAL HIT–</font><br>\\n”)]
[h: MyCommand=MyCommand+encode(” <i>Hit:</i> [NumDice*DamageDie] ([NumDice]d[DamageDie]) + [DamageBonus] + [CritBonus] = <b>[MaxDamage]</b> [DamageString]<br>\\n”)]
[h: MyCommand=MyCommand+encode(” };\\n”)]
[h: MyCommand=MyCommand+encode(” {<i>Hit:</i> [DamageRoll] ([NumDice]d[DamageDie]) + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]<br>}\\n”)]
[h: MyCommand=MyCommand+encode(“]\\n”)]
};
{[h: MyCommand=MyCommand+encode(“<i>Hit:</i> “+HitStringNoDamage+”\\n”)]
}
]
};{}
]

And now we have the “punch line” part of the code for the single-target attack case. This part of the code displays the result of the attack and damage rolls in the chat window.

[h, if(NumberOfTargets==”Multi-Target” && AttackRollRequired==1), CODE: {
[h: MyCommand=MyCommand+encode(“[h: x=input(‘NumberOfTargets|0|Number of targets’)]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: abort(x)]\\n\\n”)]
[h: MyCommand=MyCommand+encode(“Attacking [NumberOfTargets] [if(NumberOfTargets==1,’target’,’targets’)].<br>\\n”)]
[h: MyCommand=MyCommand+encode(“[count(NumberOfTargets,'<br>’), CODE: {\\n”)]
[h: MyCommand=MyCommand+encode(” [h: d20roll=d20]\\n”)]
[h: MyCommand=MyCommand+encode(” [h: AttackRoll=d20roll+AttackBonus]\\n”)]
[h: MyCommand=MyCommand+encode(” <i>Target [r:roll.count+1]:</i> [d20roll] + [AttackBonus] = <b>[AttackRoll]</b> versus [Defense]\\n”)]
[h, if(DamageRollRequired==1), CODE: {
[h: MyCommand=MyCommand+encode(” [if(d20roll==20), CODE: {<font color=Red> –CRITICAL HIT–</font> [NumDice*DamageDie] ([NumDice]d[DamageDie]) + [DamageBonus] + [CritBonus] = <b>[MaxDamage]</b> [DamageString]};{} ]\\n”)]
[h: MyCommand=MyCommand+encode(” }\\n”)]
[h: MyCommand=MyCommand+encode(“]<br><br>\\n”)]
[h: MyCommand=MyCommand+encode(“<i>Hit:</i> [DamageRoll] ([NumDice]d[DamageDie]) + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]<br>\\n”)]
};
{[h: MyCommand=MyCommand+encode(“<i>Hit:</i> “+HitStringNoDamage+”\\n”)]
}
]
}
]

Same thing, but for the multi-attack case.

[h, if(AttackRollRequired==0&&DamageRollRequired==1): MyCommand=MyCommand+encode(“<i>Hit:</i> [DamageRoll] ([NumDice]d[DamageDie]) + [DamageBonus] = <b>[RegularDamage]</b> [DamageString]<br>}\\n”)]

In the odd case where you have no attack roll but a damage roll (such as an auto-hit power that still rolls for damage), we need to display the appropriate string.

[h, if(MissEffectIncluded==1): MyCommand=MyCommand+encode(“<i>Miss:</i> “+MissEffect+”<br>\\n”)]
[h, if(AftereffectIncluded==1): MyCommand=MyCommand+encode(“<i>Aftereffect:</i> “+Aftereffect+”<br>\\n”)]
[h, if(PostAttackEffectIncluded==1): MyCommand=MyCommand+encode(“<i>Effect:</i> “+PostAttackEffect+”<br>\\n”)]
[h, if(SpecialEffectIncluded==1): MyCommand=MyCommand+encode(“<i>Special:</i> “+SpecialEffect+”<br>\\n”)]
[h, if(AttackRollRequired==0): MyCommand=MyCommand+encode(“<i>Effect:</i> “+JustEffect+”<br>\\n”)]

We now display the extra lines if they were included. Note that JustEffect is only displayed in the “no attack roll” case.

[h: FontColor=”green”]

Setting another default here; my at-will powers default to green,

[h, if(Usage==”Encounter”), CODE: {
[h: EncountersArray=getProperty(“EncounterPowersDeclared”)]
[h: FirstEmptyEncounter=-1]
[h, for(i, 9, -1, -1), CODE:
{[h: PowerCheck=json.get(EncountersArray,i)]
[h, if(PowerCheck==0): EncounterPowerNumber=i]
}
]
[h: FontColor=”red”]
[h: NewArray=json.set(EncountersArray, EncounterPowerNumber, 1)]
[h: setProperty(“EncounterPowersDeclared”, NewArray)]
[h: MyCommand=MyCommand+encode(“[h: PowerNumber=”+EncounterPowerNumber+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: PowersCharged=getProperty(‘EncounterPowersCharged’)]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: IsPowerAvailable=json.get(PowersCharged,PowerNumber)]\\n\\n”)]
[h: MyCommand=MyCommand+encode(“[h, if(IsPowerAvailable==0), CODE:\\n”)]
[h: MyCommand=MyCommand+encode(” {[assert(1==0,add(‘This power has already been expended.’),0)]};\\n”)]
[h: MyCommand=MyCommand+encode(” {[h: NewArray=json.set(PowersCharged,PowerNumber,0)]\\n”)]
[h: MyCommand=MyCommand+encode(” [h: setProperty(‘EncounterPowersCharged’, NewArray)]}]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: setMacroProps(getMacroButtonIndex(), ‘color=darkgray’) ]”)]
}
]

All right, this is admittedly a complicated piece of code. I’m proud of it, but you’ll have to bear with me.

The first line sets up that we’re only doing the remaining lines for Encounter powers. We then fetch the EncounterPowersDeclared array that I described earlier. This is the one that starts off as ten zeroes.

We then start with the last element of the array (element nine, since MapTool starts counting from zero) and check to see if that last element is a zero. If so, we set EncounterPowerNumber equal to the element number (9).

We then repeat with the next-to-last element of the array (item 8) and check to see if IT is zero. If so, we overwrite EncounterPowerNumber with 8. We keep going through the first element in the array (item 0). Whatever the last non-zero element of the array is, that becomes our new EncounterPowerNumber. And you’ll note that the FOR loop goes to -1 because it doesn’t execute an iteration where the iterator equals the end point. Yeah, it’s weird.

From here, we set the font color of the macro button to red (for an encounter power) and then set the token’s EncounterPowersDeclared array to put a 1 instead of a 0 in this new macro’s spot in the array.

I finish by adding the appropriate new lines to the macro I’m creating. These check to see if the appropriate slot in the array still has the power charged. If so, it de-charges it and moves on. If not, it throws off an error message, saying that the power is already expended.

[h, if(Usage==”Recharge”), CODE: {
[h: RechargeArray=getProperty(“RechargePowersDeclared”)]
[h: FirstEmptyRecharge=-1]
[h, for(i, 9, -1, -1), CODE:
{[h: PowerCheck=json.get(RechargeArray,i)]
[h, if(PowerCheck==0): RechargePowerNumber=i]
}
]
[h: FontColor=”purple”]
[h: NewArray=json.set(RechargeArray, RechargePowerNumber, 1)]
[h: setProperty(“RechargePowersDeclared”, NewArray)]
[h: MyCommand=MyCommand+encode(“[h: RechargeTarget=”+RechargeTarget+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: RechargeRoll=d6]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: PowerNumber=”+RechargePowerNumber+”]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: PowersCharged=getProperty(‘RechargePowersCharged’)]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: IsPowerAvailable=json.get(PowersCharged,PowerNumber)]\\n\\n”)]
[h: MyCommand=MyCommand+encode(“[h, if(IsPowerAvailable==0 && RechargeRoll<RechargeTarget), CODE:\\n”)]
[h: MyCommand=MyCommand+encode(” {[g: assert(1==0,add(‘Recharge roll = ‘, RechargeRoll, ‘. The power fails to recharge.’),0)]};\\n”)]
[h: MyCommand=MyCommand+encode(” {[h: NewArray=json.set(PowersCharged,PowerNumber,0)]\\n”)]
[h: MyCommand=MyCommand+encode(” [h: setProperty(‘RechargePowersCharged’, NewArray)]}]\\n”)]
[h: MyCommand=MyCommand+encode(“[h: setMacroProps(getMacroButtonIndex(), ‘color=gray’) ]”)]
}
]

This is the same kind of thing, but for recharge powers. The button font is purple instead of red. And when it comes time in-macro to see if the power has been expended, there’s also a recharge roll made. If the roll succeeds, then the power proceeds.

[h, if(Usage==”Not Applicable”): FontColor=”black”]

If we’re talking about a trait rather than an at-will or encounter or recharge power, then the button text should be black.

[h: MacroProps='{“autoexecute”:1, “label”:”‘+MacroName+” “+TargetRange+'”, “group”:”‘+ActionType+'”, “sortBy”:’+SortOrder+’, “fontColor”:”‘+FontColor+'”, “command”:”‘+decode(MyCommand)+'”}’]
[h: createMacro(MacroProps)]

And at long last, we actually build the macro! We stick together the JSON object that contains all of the macro properties, including decoding the MyCommand string that we’ve been building for the past 150 lines of code, and we then create a macro with those properties!

Whew – I need a drink or something.

I’m happy to say that this thing works really, really well for me so far. I’m sure I’ll tweak it over time, but I’m excited about it.

Congratulations if you’ve made it this far!

– Michael the OnlineDM

Marvel Heroic RPG – MapTool Framework – Second Draft

The blog post about my first draft can be found here. You can download the new framework right here!

Over the weekend, I had some time to improve my MapTool framework for the Marvel Heroic Roleplaying game. @MattHawke provided the key assistance: He showed me how to use custom frames to create a very, very useful character sheet.

Frames in MapTool include the Selection frame (which holds the buttons with various macros for each token), the Chat frame (which shows the output of the chat window and macros), and the Campaign frame (which holds macros common to everyone in the campaign). You can also create your own frame to hold whatever you want.

The syntax for doing this is surprisingly simple: You just use the frame roll option, followed by the name you want to give this new frame (held inside quotation marks inside parentheses). You then fill up your new frame, largely using HTML. Since I wanted to create a frame called DataFile (the Marvel RPG name for a character sheet), I used the following code:

[frame("DataFile") : {

From there, I used a bunch of HTML to fill the frame:

<html>
 <head>
 <title>[r:getProperty("Name", MyToken)] ([r: getProperty("RealName", MyToken)])</title>
 </head>
<body>

The other awesome MapTool function that @MattHawke showed me is the macroLink function. This lets you create a link to a macro within your HTML frame. The macroLink function lets you provide the linked text, the function that’s linked to, and the people to whom the output of the function should be displayed.

This allowed me to make the DataFile frame into the entire character interface. My goal is to make it so that players will never have to use the Selection window except at the very beginning of the session, to open the DataFile frame. After that, all of the stuff they can do can be done via links in the Data File itself.

I also decided it would make my life easier if I created a library token to hold all of the macros, and then simply call those macros from the Data File.

I know that other MapTool frameworks already do a lot of cool stuff with custom frames; I never really understood them until now. This is a much better way to use a character sheet, rather than looking at the pop-up window that MapTool uses by default. I’d love to re-work my D&D 4e framework to take advantage of this, but man, that would be a lot of work at this point! For now, the Marvel RPG fans are the ones who will benefit.

Please check out this updated framework. It’s light-years better than what I started with in the first draft. Feedback is invited!

– Note that you’ll need to set your stack size in MapTool to at least 4 in order for this framework to work properly. I personally set mine to 10. This is an option in the pop-up window that opens when MapTool first starts. The default stack size of 2 doesn’t cut it.

– Michael the Online Dungeon Master

Quick Hits: January 2012

After taking a brief, inadvertent break from blogging recently, I thought I’d jump back in with some quick-hit thoughts on D&D Next, my own D&D campaigns, some non-RPG games I’ve played, the Order of the Stick Kickstarter, and the upcoming Genghis Con.

D&D Next

I’m liking most of what I’m hearing out of DDXP. Simplicity if you want it, complexity if you want it, and a real effort to reach out to players of all editions. The Dice Monkey Radio bonus episode featured yours truly discussing the game with some other folks in the D&D community. I can’t wait to get my 1e books! Yay for potential unity!

My D&D Campaigns

I’ve had some real life stuff interfering with my gaming for the past couple of months, and my Friday night MapTool game is currently on hiatus. Fortunately, it looks like the real life stuff has cleared up, and I should be able to resume that game soon. Yay!

My Madness at Gardmore Abbey and ZEITGEIST campaigns have been similarly delayed, though less explicitly so. I’m hopinh to run a session of Gardmore this weekend. I was thrilled to discover that Tracy Hurley, Sarah Darkmagic herself, included a link to my write-ups of my Gardmore Abbey sessions in her article over on the Wizards of the Coast D&D page. Woo hoo!  Thanks again, Tracy!

I’m also working on a new adventure in response to a reader question. I received a request for some basic “DM 101” tips. I provided a few in an email (this came during the real-life-interfering time, so I didn’t write much), but I definitely want to make this into a blog post. And I think an important DM 101 thing to provide is a good intro adventure. I did a lot of brainstorming on planes last week and am hoping to build the adventure itself and run it in the next couple of weeks.

Non-RPG games

I got a copy of Innovation for Christmas and have played it several times. Cool little game, and it travels well! I was in Florida and Pennsylvania last week, and a friend in Florida and family members in Pennsylvania all enjoyed it. 

When I was in my FLGS recently, I saw a copy of Kittens in a Blender. In real life I’m a cat lover, even fostering kittens for the local animal shelter. Still, I couldn’t resist – in part because the game makers apparently donate part of the proceeds to a no-kill shelter.

I finally tried the game with my wife Monday night, and it’s about what I expected – rather goofy! I think it would play much better with more than two players, of course. I find that there’s an interesting tension in the game when your “best” move is to go ahead and hit the Blend button in order to save your kittens who have gotten away, but when you have a kitten or two who will face death in the machine. The illustrations are adorable, and they make it hard to consign the fluffies to die. And that’s a good thing!

Genghis Con

The local convention is a couple of weeks away, and I’m excited! I’ll be running my Staff of Suha trilogy, and I have players signed up for all three slots. I was disappointed to learn that the Hero System game based on Dr. Horrible that I was registered for was canceled, but I’m still looking forward to trying some Dresden Files and Ashes of Athas.

Order of the Stick books

I’m guessing most people who read my blog probably know about Order of the Stick, Rich Burlew’s awesome web comic about a stick figure band of D&D adventurers. I own all of the books except War and XPs, and was therefore very excited when Rich announced a Kickstarter project to get that book back in print.

He needed to raise almost $60,000 in order to get the book in print. He blew through that in about a day. As of this writing the Kickstarter has been up for a little over a week, and he’s raised nearly $300 grand and counting. This pretty much means that all of the OotS books will be in print. Yay!

My only frustration: The comic is so popular that all of the special rewards (such as those involving signed copies of the books) are snatched up before I have a chance to sign up for them! Oh well; good for Rich!

If you love OotS, I recommend supporting this Kickstarter.

Looking ahead

Now that real life seems to have gotten out of the way, I’m looking forward to more gaming goodness. My new web site should be going live soon, and I’m excited about the improved look and feel. Fun things are afoot!

-Michael the Online Dungeon Master

OnlineDM1 on Twitter

Campaign session zero: Group character creation

I’ve mainly been a 4th Edition D&D Dungeon Master. I had a little experience with 3rd Edition, but nothing before that, and I hadn’t run any games regularly until mid 2010. Because my players have had access to the extremely useful Character Builder program, character creation has usually been a solitary activity. Everyone creates their own character at their own home, perhaps exchanging ideas via email to make sure that we end up with a relatively balanced party, and then there’s a little bit of trying to make the characters fit with one another story-wise at the first session.

This weekend, I tried something different. I’ve known for a while that I wanted to run the ZEITGEIST campaign from EN World, and my regular in-person group seemed like the right people to try it with. One of my players, Bree, has been in massive crunch time in art school for months and has been out of gaming, but that’s finally done now (congrats, Bree!) and she’s ready for some D&D.

Because ZEITGEIST is a more story-focused campaign than I’ve generally run, I knew it would work better if the characters in the party had a strong connection to the world and to one another. I first floated the idea of the campaign to the players after an earlier session of a different campaign a few weeks prior, just to gauge their reactions. They seemed intrigued, so I sent them the players guide for the campaign (which you can get here).

I scheduled session zero of the campaign this past Sunday. I told everyone to bring their existing characters for the campaign that we were wrapping up, but also to have a look at the ZEITGEIST campaign guide if they had time and to start thinking about character ideas. I sent a reminder email about this the day before the session.

When everyone arrived, they seemed excited about starting a new campaign together. One person suggested that we fully roll up characters right at the table – and to use dice to generate ability scores rather than point buy. This was fine with me, so we went with 4d6, drop the lowest, assign the six scores as you like.

Thus, my recommended steps for Session Zero of a new campaign:

Step 1: Tell the players about the campaign at least a week ahead of time. Since this was a published campaign, I sent them the players guide. Had it been a home brew, I would have described whatever made my idea special and unique, so that they could “get” the idea of the campaign and start thinking about character ideas.

Step 2: Schedule a session specifically for character creation. Since we also like to actually play D&D, too, I suggest still having a one-shot game with existing characters as a side show to the main event of character creation (ideally your players won’t be jumping right in with the new characters – see step 10).

Step 3: Sit down together and talk about the character hooks for the world. In the case of ZEITGEIST, this meant the eight campaign-specific character themes, which I explained were recommended but not required. In a different campaign, this could be talking about the different regions of the world that the PCs might hail from, or unique ways that particular races or classes are viewed in this world.

Step 4: Ask if anyone is particularly intrigued by any of the hooks, and if anyone already has strong feelings about what race and/or class they want to play. Let the people who already have ideas here be the first to speak up.

Step 5: As the rest of the players one by one what appeals to them or not about the options that are out there. If they’re non-committal at this point, that’s okay; ask if they have any feelings about something broad, like the combat role they want to play. If a player is willing to fill in whatever role is needed, no problem. You can come back to that player.

Step 6: Start going through specific class (and later, race) options. I used the Character Builder for this, but solely as a convenient all-inclusive list of the classes. If someone wants to be a controller, present them with the different controller classes and say a few words about what each class is like and the ways in which that class might fit into the world or the ways in which you would re-fluff it for this world. Jump around a bit from player to player in this process.

Step 7: As people start getting their classes chosen, start handing out books (if the players don’t have their own) and blank character sheets. I liked getting to use my physical books for a change, handing Heroes of the Feywild to the person rolling up a Witch and Players Handbook 2 to the player rolling up a Bard and so on.

Step 8: Generate ability scores. We used 4d6, drop the lowest, and we went one by one around the table so that everyone could watch. This was surprisingly fun to do! Point buy would have been fine, too, though. Start assigning those scores to the abilities, and adding in racial bonuses as the players make their race selections.

Step 9: Talk about the choices that everyone is making. There’s a lot of opportunity for give and take at this point. The players will want to get one another’s (and the DM’s) input on the different options available. Maybe someone will suggest a class or race change, either because of the way the character is shaping up, or in an effort to make characters fit with one another. Perhaps someone will suggest a name for someone else’s character. This a good thing!

Step 10: Set character creation aside until the next session. At this point, the players who have the Character Builder will probably want to get their characters set up in the program so they can browse feats and more powers and so on, and perhaps even reconsider their race or class choices. That’s okay. Let them do the fine-tuning between sessions before actually running the new character.

I have to say that I think this process went really, really well. The players seemed to have fun, and their characters definitely make more sense in the world of this campaign and relative to one another then they would have if everyone had created characters on their own.

Once this process was done, we had some food to eat and then played a one-shot game with characters that they already had from an existing campaign. We set a date for our first actual ZEITGEIST session sometime over Christmas weekend. I can’t wait to get it going!

-Michael the OnlineDM

OnlineDM1 on Twitter

OnlineDM Mailbag #1: Simple IF statements in MapTool

A suggestion from Benoit (who, based on this blog post, seems to be the person behind Roving Band of Misfits) prompted me to start a mailbag series. Welcome to issue #1! I’d love to get more mailbag questions, so if you have an issue you’d like to see me address, please drop me a line at onlinedungeonmaster@gmail.com.

Question from Benoit:

I have a lot of trouble with simple “If” statements. I found a few examples in the forums, but I can’t seem to write my own.

Answer from OnlineDM:

Thanks for the question, Benoit! IF statements are an important part of most programming languages, including MapTool. Let’s do some examples

Concept #1: There are two different IF statements in MapTool

MapTool is a bit of an odd language in that you can have two different structures for the same thing. An IF statement, for instance, can either be a roll option or a function.

IF statement roll option

A roll option comes “before the colon” in MapTool macros. One common roll option is h: which causes the result of the line of code to be hidden (useful for assigning a value of a variable without printing anything to the chat window).

The syntax for an IF statement roll option is:

  • [if(Foo>Bar): Thing you do if Foo is greater than Bar; Thing you do if Foo is not greater than Bar]

For instance, here’s a very simple two-line macro

[h: Variable=InputVariable]
[if(Variable>10): "The variable is greater than ten"; "The variable is not greater than ten"]

The first line brings up an input box that asks the user to type in a number, which is assigned to Variable. The second line uses the IF roll option to see if Variable > 10 and then displays the appropriate message.

Note that you don’t have to specify the “if not” part of this second line if you don’t want to. If I want to display a message if Variable>10 but do nothing otherwise:

[h: Variable=InputVariable]
[if(Variable>10): "The variable is greater than ten"]

In this particular case, I’d output a blank line to the chat window if Variable is less than or equal to ten. This is more useful in cases where I’m doing things behind the scenes that are hidden rather than outputting them to the chat window.

Note that if you have another roll option you want to use at the same time as the IF roll option (such as the h: roll option to make the output hidden), you’ll put that other roll option before the IF statement, separated by a comma:

[h: Variable=InputVariable]
[h, if(Variable>10): "The chat window won't actually display this because I used the h roll option, too!"]

If you want to do just one thing in the case where a particular condition is true, the simple IF roll option is a good way to do it.

The IF roll option with the CODE roll option

What if you want to do multiple things if a particular condition is true? Then you want to use the CODE roll option alongside the IF roll option.

[h: Variable=InputVariable]
[if(Variable>10), CODE:
 {
 Can you believe the variable is greater than 10?
 [h: DoubleVariable=Variable*2]
 And if you double the variable, you get [DoubleVariable]!
 };
 {
 What do you know? The variable is not greater than 10.
 [h: HalfVariable=Variable/2]
 And if you cut the variable in half, you get [HalfVariable].
 }
]

Now instead of just doing one thing when a particular condition is true, I can do several. I accomplish this by sticking the CODE statement after the IF statement (separated by a comma) and then the colon. Then I have the code to execute for the “true” case of the IF statement enclosed in curly braces, followed by a semicolon and then the code to execute for the “false” case of the IF statement inside another set of curly braces.

I can do as much stuff as I like within those curly braces. Note that once I’m in curly braces, it’s just like being outside of square brackets. Text typed normally will be printed straight to the chat window, and any code I want to execute (such as assigning variables or printing their values) is enclosed in square brackets. I can even do more IF statements and CODE blocks inside these curly braces if I need to (though you can only go two layers of curly braces deep in MapTool).

The IF function

The other way to test a condition using an IF statement is to use the IF function. Functions in MapTool come “after the colon”.

[h: Variable=InputVariable]
[h: NewVariable=if(Variable>10, Variable*2, Variable/2)]
The new variable is [NewVariable].

In this example, the IF statement comes after the colon. I’m specifically using the IF function here to assign the value of a new variable, conditional on an existing variable. The syntax is completely different:

  • [NewFoo = IF(Foo>Bar, Value of NewFoo if Foo is greater than Bar, Value of NewFoo if Foo is not greater than Bar)]

You’ll note that now the “what to do” part for both the true and false cases are within the parentheses of the IF statement instead of outside the parentheses as they were for the IF roll option. They’re separated from the condition being tested by a comma (instead of a closed parenthesis and colon as in the roll option), and the “true” and “false” options are separated from one another by a comma as well (instead of a semicolon as in the roll option).

There’s no way to use CODE blocks here. So far in my own MapTool programming, I’ve only used the IF function for the particular purpose of assigning the value of a variable or other very simple tasks. I use the IF roll option much more frequently.

Concept #2: How to test conditions

I’ll wrap up with some notes on syntax. The syntax to check whether Foo is greater than Bar, Foo is less than Bar, Foo is greater than or equal to Bar and Foo is less than or equal to Bar is all exactly what you would expect:

[If(Foo>Bar): ... ]
[If(Foo<Bar): ... ]
[If(Foo>=Bar): ... ]
[If(Foo<=Bar): ... ]

Less obvious is how to check whether Foo is not equal to Bar – you use an exclamation point followed by an equals sign:

[If(Foo!=Bar): ... ]

The worst of all comes on the check you’ll likely use most often: Seeing whether Foo is equal to Bar. For this, you need to use a double equals sign:

[If(Foo==Bar): ... ]

If you try what you’ll no doubt try at some point [if(Foo=Bar):…], you’ll get an error message from MapTool.

I’ll also note that you might want to check to see whether a variable is equal to a particular string (some text rather than a number). In that case, you need to make sure the value you’re checking for is enclosed in quotes.

[h: Variable=FavoriteColor]
[if(Variable=="Blue"): "My favorite color is blue, too!"; "I see. Well, I prefer blue."]

Note the syntax. First, I’m using the IF roll option (before the colon), not the function. Second, I’m using the double equals sign to check for equality. Third, I’m enclosing “Blue” in quotes to see if that’s what’s been entered for Variable. If I enter Red or Nothing or 75 into the input box, I’ll get a message in the chat window saying “I see. Well, I prefer blue.” But if I enter Blue into the input box, I’ll get a message in the chat window saying “My favorite color is blue, too!”

If I forget the quotes around the word Blue in parentheses, I’ll get a second input box asking me for the value of Blue. MapTool will think that Blue is a variable that I haven’t assigned a value to, instead of a string that I want to check Variable against.

Wrap-up

Well Benoit, I hope you found this to be helpful! MapTool is a bit of a wonky language, but it can get you where you need to go most of the time. Understanding the ins and outs of the IF roll option and the IF function will take you a long way.

Remember everyone, send me your future mailbag questions at  onlinedungeonmaster@gmail.com.

-Michael the OnlineDM

OnlineDM1 on Twitter

Skills in D&D 4e part 2: Player skill versus character skill

In part 1 of my skills series, I talked about passive skill use and rewarding players who choose to train skills. Now it’s time to share my views on player skill versus character skill.

It’s useful to note my D&D background here. I had a hint of exposure to D&D 2e when I was about 13 or 14 years old, but never even played a single session. I got the books and learned Third Edition in the early 2000s but only played a few one-on-one sessions with my wife and then one session with a couple of friends before things petered out. So, most of my real D&D experience has come with Fourth Edition since early 2010.

The old school approach

As I understand things, earlier editions of D&D (especially prior to 3e) tended to focus more on the skill of the player sitting at the table than the skill of the character in the game for things like social interaction, searching and puzzle solving.

If a player wanted her character to convince the town guard to let her through the gate after hours, the player would try her hand at making a moving speech, or telling a convincing lie, or scaring the guard into backing down, or whatever. The DM would then judge whether she had made a good enough play to get through the gate.

If a rogue was searching a room looking for hidden doors and secret treasure, the rogue’s player would describe going to each corner of the room, tapping on the floorboards, feeling around the window frame for catches, moving the rug out of the way to look for trap doors, and so on. If he searched in the right place and in the right way, he’d find the secret. If not, then not.

If the party were confronted with a puzzle, the players around the table would put their heads together and try to figure it out. They might beg the DM for hints, and he might or might not give them out.

The new school approach

In 4th Edition, things work a little differently. The player whose PC was trying to get by the town guard would likely be asked by the DM to make a Diplomacy, Bluff or Intimidate check, perhaps with a +2 bonus for good role-playing. A good roll of the die can overcome a lousy speech, a transparent lie or a meek threat.

The player whose rogue was  searching a room would be asked to make a Perception check. If he wanted to really take his time and search extra carefully, the player might tell the DM that he wanted to take 20 (more for 3.X than 4th Edition) and be sure to find every possible secret.

The party confronted with a puzzle might be told to make an Intelligence or Insight check to get a hint – or to possibly solve the puzzle outright.

My approach

Like most DMs, I tend to do things my own way, but I’m definitely more new school than old school when it comes to skill use at the table. I default to challenging the character rather than the player in most instances.

The logic of this approach is consistency. I don’t require the player to demonstrate the ability to pick a real-world lock in order to use Thievery or to lift a heavy object in order to use Athletics; why would I require the player to make a real-world speech in order to use Diplomacy? Why should they have to demonstrate that they (the player) know where things are likely to be hidden in order to use Perception?

That said, I certainly want immersion in my games, and I absolutely reward players with bonuses for being creative and entertaining in whatever they’re trying. If they actually do a good job of speaking in-character for their Diplomacy check, I’ll given them a +2 bonus to the check as well as a bonus point. If it’s a really fantastic speech or lie or whatever, I might just say “Success!” with no roll needed.

If they look at the map of the room and say, “You know, that bookcase looks a little out of place; can I check to see if pulling on any of the books triggers a secret door?” then I might just say “Success!” with no roll needed.

If they’re working on a puzzle, I’ll probably set things up so that they can solve it as players, perhaps using character skills to get a hint, but I’ll also give them an option to handle the whole thing with skills in case my particular group of players isn’t into doing puzzles. A good example of this is the Room of Runes puzzle in my Descent Into Darkness adventure (page 7-9 of the PDF). The players can solve it as a puzzle, but if that’s not their style, they can just use skill checks to get through the room without actually dealing with the puzzle’s solution.

Reward skilled players, but don’t penalize unskilled players

You might be complaining at this point: “Hey OnlineDM, you say that you focus on character skill more than player skill, but you just gave examples where skilled players can achieve automatic success without rolling the dice. What gives?”

Well, I admitted that my own approach was a mixture of old and new school, with a leaning toward new. What I don’t like about the pure “player skill” approach is that you can end up penalizing unskilled players, even if they’re running skilled characters.

A high-Charisma bard who’s trained in Diplomacy is going to be able to charm a barmaid into sharing some details about the last party to pass through the tavern, even if the bard’s player can barely string a coherent sentence together in real life. If that player says he wants his bard to charm the barmaid, he should be allowed to roll a Diplomacy check and succeed if the character’s skill is high enough.

In this situation, I’ll still ask the player, “What’s old silver-tongue saying to the barmaid?” in an effort to encourage some role-playing, of course. But if Tommy Tongue-Tied gets a great roll but can’t come up with something reasonable to say in-character, I don’t tell him, “Well, your bard stammers and then insults the barmaid’s mother. She tosses a mug of ale in your face and storms off.” I encourage the role-play, but if the player can’t manage it, we move on based on character skill.

Yes, this means that I’ll occasionally let a character with low Charisma and no social skill training succeed on a task that’s probably beyond their character’s abilities by role-playing the heck out of the situation, or I’ll let the low-Wisdom unperceptive character find the secret door because the player suggested looking in just the right spot. I won’t let this be abused at my table, though.

If a great role-player wants to be the face of the party but chooses to put all of her skill training in the non-social skills in a power-gamey way (“It will be just like having training in all of the social skills without having to waste my skill training slots!”) then I’m going to clamp down. A great role-player should also be able to role-play having low Charisma, for instance. If she comes up with a genius lie every now and then, despite a terrible Bluff score, I’ll go with it. But if it becomes an abuse of my approach, I’ll say, “That’s very creative, but let’s see what your character comes up with. Roll me a Bluff check.” I’d probably still hand her the bonus point for creativity, though.

This is mainly going to come up with skills tied to Charisma and Wisdom, and perhaps Intelligence to a lesser degree (recalling some piece of history from the setting’s background materials could test player skill, I suppose, instead of asking for a History check). But creative description and good role-playing can make any skill check easier at my table. If your fighter’s struggle to brace himself against the stone block that’s trying to close off the exit to the room is described in especially vivid, exciting terms, I’m going to give you a bonus to the Athletics check plus a bonus point, but a terrible roll can still result in failure.  On the flip side, if the player is absolutely convinced that the shaman is lying to him, regardless of the result of his Insight check, he still could proceed as if the shaman were lying (which, of course, the shaman might not have been after all, but the character wasn’t Insightful enough to tell).

Player skill matters, and if the players have got it, it will certainly help them at my table. But if they’re lacking in social skills or wisdom skills as actual individuals, that doesn’t mean that their characters must also be lacking when they play with me. Best of both worlds, that’s my goal!

-Michael the OnlineDM

OnlineDM1 on Twitter

MapTool macros: Condition tracking for D&D4e

At last, the culmination of my advanced MapTool topics from last week: My macros for tracking conditions in D&D 4th Edition!

Let me disclaim once again that there are lots of super-fancy campaign frameworks out there that probably handle this sort of thing far better than I do. But since I actually get pleasure from learning about MapTool macros, I write my own.

Shortcut to the end: Download the campaign file that contains all of these macros.

The goal

I already had macros that would toggle conditions like Dazed or Blinded on and off of tokens. What I wanted was a way of giving players the option of specifying an end time for the condition: Either the beginning of someone’s turn, the end of someone’s turn, or save ends (or no ending time at all, such as for Prone, which ends when you decide to get up). Then I wanted that condition to either automatically end or to see a message reminding you to save against a condition at the appropriate time.

It sounds so simple. For me, at least, it wasn’t. But I got there!

Step 1: Storing information about conditions

I already had a way of knowing whether a condition was on a token or not. MapTool has “states” that can be turned on or off, and if they’re on they display a little icon over the token. What I lacked was a way to track when the condition was supposed to end.

I ultimately went with a JSON array made up of JSON objects, stored as a property on a new library token (see the hyperlinks to understand what the heck that’s all about). I created a library token called Lib:LibToken1 and a new set of properties for it, with a single property called StatesArray. When populated, it looks something like this:

[{"TokenName":"None"},{"TokenName":"Shadow Ape","State":"Blind","EndCount":0,"EndRound":2,"AutoEnd":"1"},{"TokenName":"Torrent","State":"Quarried","EndCount":0,"EndRound":2,"AutoEnd":"1"}]

The first JSON object is just a dummy that I leave there all the time. The next object has five keys with values:

  • The name of the token affected is Shadow Ape
  • The state on it is Blind
  • The count in the initiative order in which this state should end is 0
  • The round in which the state should end is round 2
  • Yes, the state should end automatically

How do I set this up? With macros, of course!

Macro #1: Condition toggles

I already had a bunch of buttons in my campaign window that would toggle a condition on or off of a token. I just repurposed these to work with my new arrangement. Since each button’s name (with a few exceptions) matches the state that I’m turning on or off, they all have the same code, and for this example I’ll assume we’re working with the “Dazed” button.

[h: MacroArguments=json.set('{ }', "TokenName", "None", "State", getMacroName(), "EndCount", -2, "EndRound", -2, "AutoEnd", -2)]
[MACRO("ToggleState@lib:LibToken1"): MacroArguments]

The first line sets up a JSON object with two important variables: The first important variable is the name of the state I’m toggling, which is the same as the name of the macro button I’m clicking (Dazed). The second important variable is at the end of the JSON object – AutoEnd=-2. This will show up later on; it will tell my ToggleState macro that I want a pop-up to ask the player when the condition will end. The other three variables in the JSON object (TokenName, EndCound, EndRound) don’t matter for this particular macro right now.

I then use the MACRO roll option to call a macro called ToggleState that lives on a token called Lib:LibToken1, passing it the JSON object I created above.

Macro #2: ToggleState

Brace yourselves: It gets a little deeper now.

[h: SelectedTokens=getSelected()]
[h: StateName=json.get(macro.args, "State")]
[h: EndCount=json.get(macro.args, "EndCount")]
[h: EndRound=json.get(macro.args, "EndRound")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

The first line gets a JSON array that contains all of the tokens that are currently selected (this lets me make a whole bunch of tokens Dazed at once by selecting all of them and then running the Dazed macro). The next four lines unpack the elements of the JSON object that I passed over – that JSON object is in the macro.args variable since it was the argument passed to this macro. Note that it’s possible for me to pass meaningful numbers to the macro for EndCount, EndRound and AutoEnd, but in this case the relevant part is the name of the state and AutoEnd=-2.

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]

I now get the StatesArray property from the library token – that’s the JSON array that contains all of the states that are currently on tokens, along with the times that they end.

[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {
   [h: CurrentTokenName=getName(TokenID)]
   [h: NameAndState=json.set('{ }', "Name", CurrentTokenName, "State", StateName)]

I start looping through each of the selected tokens, running a piece of code on all of them. The first thing I do is to get the name of the first token I’ve selected, and then create a small JSON object that contains the name of the token and the state I’m toggling, for use later.

   [h, if(getState(StateName,TokenID)==0), CODE:
    {

I check to see if the token I’m working on is currently subject to the state in question (Dazed). If it’s not currently dazed, I run some more code (because I want to turn on the Dazed condition). I’ll run some different code (later) if I’m turning OFF the Dazed condition.

      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
      [ MACRO("GetExpirationInfo@Lib:LibToken1"): TokenStateObject]

I set up a JSON object that’s the same as the arguments that I passed over to this macro, except that I pop the name of the first token I’m working on into the TokenName slot. I then pass this object over to the GetExpirationInfo macro on the Lib:LibToken1 library token. That macro (which  you’ll see later) will ask the player for information about when the condition is supposed to end (assuming AutoEnd=-2 to start with, as it does here) and then pass that information back to this ToggleState macro.

      [ MACRO("GetExpirationInfo@Lib:LibToken1"): TokenStateObject]
      [h: EndCount=json.get(macro.return, "EndCount")]
      [h: EndRound=json.get(macro.return, "EndRound")]
      [h: AutoEnd=json.get(macro.return, "AutoEnd")]
      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]

Similar to what we saw above, I now unpack the new JSON object that comes back from the GetExpirationInfo (it’s stored as macro.return). I then rebuild the TokenStateObject JSON with the update information about the end time for the condition.

     [CurrentStatesArray=json.append(CurrentStatesArray,TokenStateObject)]
     [h: setState(StateName,1,TokenID)]
     };

I now stick this JSON object onto the end of the CurrentStatesArray JSON array variable, adding one more token/state/ending time element to the list of all of them that are currently active. I then turn the Dazed state on for the token in question, so that the Dazed icon will appear on it.

This then ends the list of tasks I’m performing specifically for the case where I’m turning the Dazed state ON; I now move on to the case when I’m turning the Dazed state OFF.

    {
     [h, MACRO("FindToken@Lib:LibToken1"):  NameAndState]
     [h: CurrentTokenStateIndex=macro.return]
     [h: CurrentStatesArray=json.remove(CurrentStatesArray, CurrentTokenStateIndex)]
     [h: setState(StateName,0,TokenID)]
    }
   ]
 }
]

I start off by calling another macro called FindToken, passing it the JSON object I created earlier that has the name of the token in question and the state we’re toggling off. That macro (which we’ll look at later on in the post) looks through the current list of tokens and states that are active to figure out where this token/state pair lives in the list, and returns the index of this token/state pair in that array. We then remove that element from the array, and finally turn the state off of the token (so the Dazed icon will no longer appear on the image).

We then close the section of code for the “toggle off” case (first curly brace), close the “if” statement (first square bracket), close the FOREACH code block (second curly brace) and close the FOREACH statement itself (second square bracket).

At this point, the whole toggling process has been run for each selected token, toggling the state itself on or off and modifying the CurrentStatesArray variable to include the token/state/end time JSON objects for the states we’ve toggled ON and to exclude the token/state/end time JSON objects for the states we’ve toggled OFF.

[h: setLibProperty("StatesArray", CurrentStatesArray, "lib:LibToken1")]

We end the whole macro by updating the StatesArray property on the Lib:LibToken1 token to equal the updated array that we’ve been creating. Now the library token will have the correct list of tokens, states and ending conditions. Voila!

Macro #3: GetExpirationInfo

This macro is called from within the ToggleStates macro and asks the user to specify when the condition that we’re applying should end. I’ll give a shout-out here to Paul Baalham; I repurposed some of his code here to create a list of token images for a drop-down.

[h: CurrentTokenName=json.get(macro.args, "TokenName")]
[h: CurrentState=json.get(macro.args, "State")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

First, I look at the JSON object that’s been passed to this macro and extract from it the name of the token we’re working on, the state we’re toggling (Dazed) and the value of the AutoEnd variable.

[h, if(AutoEnd==-2), CODE:
{

Here’s where the AutoEnd=-2 comes into play. The following code is only run if I’ve specifically set the AutoEnd variable equal to -2. It’s arbitrary, but it’s what I’ve picked.

 [h: CurrentLabel=add(CurrentState, " condition on ", CurrentTokenName)]
 [h: InitTokens=json.get(getInitiativeList(), "tokens")]

 [h: TokenImageList=""]

I set up a string called CurrentLabel that will say something like “Dazed condition on Goblin 5”.

I create a variable called InitTokens that has the list of tokens that are currently in the initiative order. Note that the output of getInitiativeList is a JSON object, one element of which is called “tokens”, which is ITSELF a JSON object, one of whose elements is the token IDs. Messy stuff.

I also set up an empty variable called TokenImage list that I’ll stick stuff into shortly.

 [h, FOREACH(Thingy, InitTokens, ""), CODE:

I then start a loop that goes through each of the tokens in the initiative window (each one referred to by me as Thingy just for the fun of it).

 {[ThisTokenID=json.get(Thingy, "tokenId")]
   [token(ThisTokenID): CurrentImage=getTokenImage()]
   [TokenImageList=listAppend(TokenImageList, getName(ThisTokenID) + " " + CurrentImage)]
 }
]

I get the token ID for each token, then get its image. Note that I have to use the “token” roll option in order to do this; I can only use the getTokenImage() function on the “active” token, and the token roll option lets me treat whatever token I like as active.

I then append the name of the token and its image to the TokenImageList variable. We’ll be using this in a little while.

 [h:x=input(
  "junkVar | " + CurrentLabel + " | Make selections for | LABEL | SPAN=FALSE",
  "TurnTime | None, Beginning, End, Save | On what part of the turn does the condition end? | RADIO | ORIENT=H SELECT=0 VALUE=STRING",
  "WhichTurn |"+TokenImageList +" | On whose turn does the condition end? | LIST | SELECT=0 ICON=TRUE ICONSIZE=30",
  "EndCurrentTurn | 0 | If end of own turn, does the condition end NEXT round? | CHECK"
 )]
 [h: abort(x)]
Now I bring up a dialog box where include a label that specifies what token and state I'm inquiring about, and I ask the user for three things. First, I have a set of radio buttons that ask whether this is a condition that doesn't end at a particular time, or one that ends at the beginning or end of someone's turn, or one that's "save ends". I save this as the TurnTime variable.

Next, I ask whose turn it ends on. Note that if you pick “save ends” you still need to choose the token’s own picture from the list. This is because I might have 10 goblin warriors on the map, but only Goblin Warrior 1 actually shows up in the initiative window. So, if Goblin Warrior 3 is dazed (save ends), I’d choose Goblin Warrior 1’s image from the drop-down list, since I want the “Make a saving throw” message to pop up at the end of the goblins’ collective turn. Note that the variable saved here, WhichTurn, is going to be the INDEX of the item I picked from the list, with the first token on the list having an index of zero.

Finally, I have a wonky workaround for the situation in which a character is subject to a condition on their own turn that ends at the end of their NEXT turn (such as “you get a +2 bonus to attack rolls until the end of your next turn”). If they check this box, it won’t end the condition at the end of the current turn, instead postponing it for a round.

The abort(x) means that if I clicked the Cancel button in the dialog box, the macro ends.

The pop-up looks something like this:

Next piece of code:

[h: CurrentInit=getCurrentInitiative()]
[h: CurrentRound=getInitiativeRound()]
[h: MaxInit=json.length(json.get(getInitiativeList(), "tokens"))-1]

I figure out what the current initiative count and round are. I then figure out the maximum initiative count. Note that I do this my counting how many things are in the current initiative list and subtracting 1. I do this because, as you may recall from the JSON arrays article, JSON array indexes START AT ZERO. So, if there are three things in the initiative order, the maximum count of the array is 2 (0, 1, 2).

 [SWITCH(TurnTime), CODE:

I now use a SWITCH statement, which will let me do different things depending on what has been selected for the ending time.

  case "None": {
    [h: EndCount=-1]
    [h: EndRound=-1]
    [h: AutoEnd=-1]
                        };

If the player said that the condition doesn’t end at any particular time, then I set EndCount, EndRound and AutoEnd all equal to -1.

  case "Beginning": {
    [h: EndCount=WhichTurn]
    [h: EndRound=if(WhichTurn>CurrentInit,CurrentRound,CurrentRound+1)]
    [h: AutoEnd=1]
                        };

If the player said that the condition ends at the beginning of someone’s turn, then I set the ending initiative count equal to the index of whatever token was picked from the drop-down (WhichTurn). If that token’s turn hasn’t come up yet this round, then the ending round is this round; otherwise, it’s next round. And I set AutoEnd=1 to tell another macro that this condition should end automatically.

  case "End": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=if(WhichTurn+1>CurrentInit && WhichTurn+1<=MaxInit,CurrentRound+EndNextRound,CurrentRound+EndNextRound+1)]
    [h: AutoEnd=1]
                        };

If this is a condition that ends at the END of someone’s turn, then I make it work the same as ending at the BEGINNING of the NEXT token’s turn. If it ends at the end of someone’s turn who’s not at the very end of the initiative order, then I simply add 1 to the index of the chosen token. On the other hand, if it ends at the end of the turn of the last token in the round, then I have to make it end at the beginning of the FIRST token in the next round (count 0).

As for choosing a round, if it ends at the end of someone whose turn is either ongoing or is coming later in the round, it ends in the current round PLUS the EndNextRound variable (in case it’s this token’s turn and the condition should end at the end of their NEXT turn). If the turn has already passed or if it’s ending at the end of the turn of the LAST token in the initiative order, then it ends another round on.

  case "Save": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=-1]
    [h: AutoEnd=0]
                        };

If we’re talking about a “save ends” condition, then the EndCount is identical to the “end of turn” logic, but there’s no particular round in which the condition ends (hence the -1 for EndRound). AutoEnd is set to 0 because the condition won’t end automatically at the end of the character’s turn – it will depend on a saving throw.

  default: {[r: "You somehow didn't pick anything!"]}
 ]

I end the SWITCH with the default option, which shouldn’t ever show up, but hey, I could have screwed something up in my code.

 [h: ReturnedVars=json.set("{ }", "EndCount", EndCount, "EndRound", EndRound, "AutoEnd", AutoEnd)]
};

I now create a JSON object that contains the EndCount, the EndRound and the AutoEnd for this particular token for the case in which AutoEnd was equal to -2, and I then close the section of “AutoEnd==-2” code.

{[h: ReturnedVars=MacroArgs]}
]

THIS is the piece of code that’s run if I didn’t set AutoEnd=-2. What’s up with that? Well, this lets me do something like have my Second Wind code automatically specify when the +2 to all defenses condition ends (at the beginning of the character’s next turn) rather than making the player pick this timing from the dialog box. It’s a minor thing, but I like having the ability to bypass the dialog box if I already know when the condition is supposed to end.

[h: macro.return=ReturnedVars]

Finally, no matter whether I popped up the dialog box to get the end timing or I already knew it before coming to this macro, I set the macro.return variable equal to this JSON object, for use back in the ToggleStates macro that called this macro. That’s it!

Macro #4: FindToken

This macro is called in ToggleStates when we’re turning a condition OFF from a token. It sorts through the current JSON array in the StatesArray property of the library token, looking for the JSON object in that array that corresponds to the token and state that we’re trying to turn off, so that the object can be removed from the array.

[h: CurrentTokenName=json.get(macro.args, "Name")]
[h: CurrentTokenState=json.get(macro.args, "State")]

I start off by getting the token name and state that we’re looking for from the JSON object that was passed to this macro as arguments. macro.args represents that JSON object that was passed, and it has two keys: Name and State. Easy enough, now that we understand JSON objects!

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]

I retrieve the current value of the StatesArray property of the library token. I initialize a variable called CurrentTokenStateIndex to be equal to -1 (so that if I don’t find this token/state pair in the array, the index I return for its location will be -1 – that is, nonexistent). I then figure out how many JSON objects are in the array right now, so that I know how many I need to loop through.

[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: CurrentTokenStateIndex=if(json.get(CurrentObject, "TokenName")==CurrentTokenName && json.get(CurrentObject, "State")==CurrentTokenState, roll.count, CurrentTokenStateIndex)]
 }
]

I use the COUNT roll option to loop through each object in the current states array. Note that COUNT also starts at zero. If I COUNT(3), I will do stuff for roll.count=0, roll.count=1 and roll.count=2 (three times, starting at zero).

I retrieve each object in the JSON array, storing it as CurrentObject, then check to see if the TokenName and the State of that object match the token/state pair that we’re looking for. If there’s a match, then I set CurrentTokenStateIndex equal to the matching index of the array that we’re looking at. Otherwise, I leave CurrentTokenStateIndex where it was. Since it started at -1, if I go through every object in the array and never find a match, it will stay at -1 the whole time.

[h: macro.return=CurrentTokenStateIndex]

The value that I return to the ToggleStates macro is the index in the StatesArray JSON array that contains the token/state pair we’re looking to turn off. Bam!

Step 2: Checking for conditions that need to end at a particular initiative count

That does it for toggling states on and off manually and storing information about them in the StatesArray. What about having them automatically end at the appropriate time in the initiative count, or at least prompting the player for a saving throw?

I love the initiative window in MapTool; I use it even for in-person games when I’m using MapTool with my projector setup. But now I need to create some of my own macros for advancing the initiative count, because I need to check some things at each count. Specifically, are there any token conditions that need to end?

Macro #5: NextInitiative

I created a button in my campaign window called NextInitiative. All this macro does is advance the initiative count and then call another macro to check to see what happens at the new count:

[h: nextInitiative()]
[MACRO("CheckForChanges@lib:LibToken1"):""]

Yes, I have another macro on the library token, this one called CheckForChanges. You’ll note that I’m passing it a set of null arguments; I can’t just leave that part out. Go figure.

Macro #6: CheckForChanges

[h: CurrentInit=getCurrentInitiative()]
[h: CurrentRound=getInitiativeRound()]
[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]

This macro begins with some simple housekeeping. I figure out what initiative count and what round we’re on at the moment. I figure out what’s in the current StatesArray JSON array that’s keeping track of all the conditions on all the tokens and when they need to end. I set a variable called CurrentTokenStateIndex to a -1 value, and I figure out how many objects are in the StatesArray. Yes, another loop is coming.

[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: ObjectTokenName=json.get(CurrentObject, "TokenName")]
  [h: ObjectState=json.get(CurrentObject, "State")]
  [h: ObjectEndInit=json.get(CurrentObject, "EndCount")]
  [h: ObjectEndRound=json.get(CurrentObject, "EndRound")]
  [h: ObjectAutoEnd=json.get(CurrentObject, "AutoEnd")]

I start with the first object in the array and retrieve all five keys from that JSON object: The token name, the state, the initiative count when the state ends, the round when the state ends, and whether the state should end automatically or not.

  [if(CurrentInit==ObjectEndInit && CurrentRound==ObjectEndRound && ObjectAutoEnd==1), CODE:
   {The [ObjectState] condition on [ObjectTokenName] has ended.
    [MACRO("ToggleState2@lib:LibToken1"):CurrentObject]
   };{}
  ]

If we’re at the right initiative count and the right round for a state to end on a token and we’ve said that this state should end automatically, then I display a message in the chat window that explains what state is ending on what token. I then call another macro, called ToggleState2, to turn that state off. It’s a lot like the original ToggleState macro, but it doesn’t require looping through a bunch of tokens (since we’ve already done that).

  [if(CurrentInit==ObjectEndInit && ObjectAutoEnd==0), CODE:
   {[ObjectTokenName] needs to make a saving throw against the [ObjectState] condition.
   };{}
  ]
 }
]

If we’re at the right initiative count for a state to end and the AutoEnd variable is zero, that means it’s time for a “save ends” message to pop up. You’ll note that we don’t actually end the condition – we just display the reminder message. We then end the COUNT loop and the macro itself.

Finally, for the sake of completeness, here is the ToggleState2 macro without comment (because it’s pretty much the same stuff as ToggleState).

Macro #7: ToggleState2

[h: ArgsArray=macro.args]
[h: SelectedTokens=getSelected()]
[h: StateName=json.get(ArgsArray, "State")]
[h: TokenName=json.get(ArgsArray, "TokenName")]
[h, TOKEN(TokenName): TokenID=currentToken()]

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h, if(getState(StateName,TokenID)==0), CODE:
  {[h, MACRO("GetExpirationInfo@Lib:LibToken1"):""]
    [h: EndCount=json.get(macro.return, "EndCount")]
    [h: EndRound=json.get(macro.return, "EndRound")]
    [h: AutoEnd=json.get(macro.return, "AutoEnd")]
    [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
   };{}
]

[h: CurrentTokenName=getName(TokenID)]
[h: FindTokenArgs=json.set('{ }', "Name", CurrentTokenName, "State", StateName)]
[h, MACRO("FindToken@Lib:LibToken1"):  FindTokenArgs]

[h: CurrentTokenStateIndex=macro.return]
[h, if(getState(StateName,TokenID)==0), CODE:
 {[NewStatesArray=json.append(CurrentStatesArray,TokenStateObject)]
   [setLibProperty("StatesArray", NewStatesArray, "lib:LibToken1")]
   [h: setState(StateName,1,TokenID)]
   };
  {[h: NewStatesArray=json.remove(CurrentStatesArray, CurrentTokenStateIndex)]
    [h: setLibProperty("StatesArray", NewStatesArray, "lib:LibToken1")]
    [h: setState(StateName,0,TokenID)]
  }
]

Whew!

Okay, so it took me seven different macros to make this work – but it does work! I’m really excited about this, and I hope that this will end the “Oh wait, when does that condition end again?” discussions at the table. We shall see.

The uninterrupted code for the longer macros is below (note that ToggleState2 is above), and if you want to download my campaign file that includes all of these macros, you can do that at this link.

As always, feedback is appreciated!

-Michael, the OnlineDM

ToggleState macro:

[h: SelectedTokens=getSelected()]
[h: StateName=json.get(macro.args, "State")]
[h: EndCount=json.get(macro.args, "EndCount")]
[h: EndRound=json.get(macro.args, "EndRound")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]

[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {
   [h: CurrentTokenName=getName(TokenID)]
   [h: NameAndState=json.set('{ }', "Name", CurrentTokenName, "State", StateName)]
   [h, if(getState(StateName,TokenID)==0), CODE:
    {
      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
      [ MACRO("GetExpirationInfo@Lib:LibToken1"): TokenStateObject]
      [h: EndCount=json.get(macro.return, "EndCount")]
      [h: EndRound=json.get(macro.return, "EndRound")]
      [h: AutoEnd=json.get(macro.return, "AutoEnd")]
      [h: TokenStateObject=add('{"TokenName":"', getName(TokenID), '", "State":"', StateName, '", "EndCount":', EndCount, ', "EndRound":', EndRound, ', "AutoEnd":"', AutoEnd, '"}')]
     [CurrentStatesArray=json.append(CurrentStatesArray,TokenStateObject)]
     [h: setState(StateName,1,TokenID)]
     };
    {
     [h, MACRO("FindToken@Lib:LibToken1"):  NameAndState]
     [h: CurrentTokenStateIndex=macro.return]
     [h: CurrentStatesArray=json.remove(CurrentStatesArray, CurrentTokenStateIndex)]
     [h: setState(StateName,0,TokenID)]
    }
   ]
 }
]

[h: setLibProperty("StatesArray", CurrentStatesArray, "lib:LibToken1")]

GetExpirationInfo macro:

[h: CurrentTokenName=json.get(macro.args, "TokenName")]
[h: CurrentState=json.get(macro.args, "State")]
[h: AutoEnd=json.get(macro.args, "AutoEnd")]

[h, if(AutoEnd==-2), CODE:
{
 [h: CurrentLabel=add(CurrentState, " condition on ", CurrentTokenName)]
 [h: InitTokens=json.get(getInitiativeList(), "tokens")]

 [h: TokenImageList=""]

 [h, FOREACH(Thingy, InitTokens, ""), CODE:
  {[ThisTokenID=json.get(Thingy, "tokenId")]
    [token(ThisTokenID): CurrentImage=getTokenImage()]
    [TokenImageList=listAppend(TokenImageList, getName(ThisTokenID) + " " + CurrentImage)]
  }
 ]

 [h:x=input(
  "junkVar | " + CurrentLabel + " | Make selections for | LABEL | SPAN=FALSE",
  "TurnTime | None, Beginning, End, Save | On what part of the turn does the condition end? | RADIO | ORIENT=H SELECT=0 VALUE=STRING",
  "WhichTurn |"+TokenImageList +" | On whose turn does the condition end? | LIST | SELECT=0 ICON=TRUE ICONSIZE=30",
  "EndNextRound | 0 | If end of own turn, does the condition end NEXT round? | CHECK"
 )]
 [h: abort(x)]

 [h: CurrentInit=getCurrentInitiative()]
 [h: CurrentRound=getInitiativeRound()]
 [h: MaxInit=json.length(json.get(getInitiativeList(), "tokens"))-1]

 [SWITCH(TurnTime), CODE:
  case "None": {
    [h: EndCount=-1]
    [h: EndRound=-1]
    [h: AutoEnd=-1]
                        };
  case "Beginning": {
    [h: EndCount=WhichTurn]
    [h: EndRound=if(WhichTurn>CurrentInit,CurrentRound,CurrentRound+1)]
    [h: AutoEnd=1]
                        };
  case "End": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=if(WhichTurn+1>CurrentInit && WhichTurn+1<=MaxInit,CurrentRound+EndNextRound,CurrentRound+EndNextRound+1)]
    [h: AutoEnd=1]
                        };
  case "Save": {
    [h: EndCount=if(WhichTurn<MaxInit,WhichTurn+1,0)]
    [h: EndRound=if(WhichTurn+1>CurrentInit && WhichTurn+1<=MaxInit,CurrentRound,CurrentRound+1)]
    [h: EndRound=-1]
    [h: AutoEnd=0]
                        };
  default: {[r: "You somehow didn't pick anything!"]}
 ]
 [h: ReturnedVars=json.set("{ }", "EndCount", EndCount, "EndRound", EndRound, "AutoEnd", AutoEnd)]
};
{[h: ReturnedVars=MacroArgs]}
]
[h: macro.return=ReturnedVars]

FindToken macro:

[h: CurrentTokenName=json.get(macro.args, "Name")]
[h: CurrentTokenState=json.get(macro.args, "State")]
[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]
[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: CurrentTokenStateIndex=if(json.get(CurrentObject, "TokenName")==CurrentTokenName && json.get(CurrentObject, "State")==CurrentTokenState, roll.count, CurrentTokenStateIndex)]
 }
]
[h: macro.return=CurrentTokenStateIndex]

CheckForChanges macro:

[h: CurrentInit=getCurrentInitiative()]
[h: CurrentRound=getInitiativeRound()]
[h: CurrentStatesArray=getLibProperty("StatesArray","lib:LibToken1")]
[h: CurrentTokenStateIndex=-1]
[h: StatesArrayLength=json.length(CurrentStatesArray)]
[COUNT(StatesArrayLength, ""), CODE:
 {[h: CurrentObject=json.get(CurrentStatesArray, roll.count)]
  [h: ObjectTokenName=json.get(CurrentObject, "TokenName")]
  [h: ObjectState=json.get(CurrentObject, "State")]
  [h: ObjectEndInit=json.get(CurrentObject, "EndCount")]
  [h: ObjectEndRound=json.get(CurrentObject, "EndRound")]
  [h: ObjectAutoEnd=json.get(CurrentObject, "AutoEnd")]
  [if(CurrentInit==ObjectEndInit && CurrentRound==ObjectEndRound && ObjectAutoEnd==1), CODE:
   {The [ObjectState] condition on [ObjectTokenName] has ended.
    [MACRO("ToggleState2@lib:LibToken1"):CurrentObject]
   };{}
  ]
  [if(CurrentInit==ObjectEndInit && ObjectAutoEnd==0), CODE:
   {[ObjectTokenName] needs to make a saving throw against the [ObjectState] condition.
   };{}
  ]
 }
]

Advanced MapTool macros part 3: Library tokens and the MACRO roll option

As you may have read in my earlier posts about JSON objects and JSON arrays, I’ve been working on macros to let me better track conditions on tokens for my D&D 4e game in MapTool. I thought it would be simple, but I ultimately needed to learn about some rather advanced topics. This includes today’s topics: library tokens and the MACRO roll option.

What’s a library token?

A library token is pretty much like any other token, except that its name must begin with “Lib:” (including the colon, but not the quotes). For instance, I created a token called Lib:LibToken1. I’m creative like that.

What’s the point?

While there might be tons of cool things you can do with library tokens, I’ve discovered two so far.

First, they’re handy for storing properties that you’ll want to reference across multiple macros. You theoretically could do this with any token, but library tokens are available no matter what map you’re on, and you’re less likely to accidentally delete them if they get killed (since, you know, they’re not intended to actually be in combat).

Second, if you put a macro called onCampaignLoad on a library token, then it will automatically run whenever you open up the campaign (or whenever your players connect to it). I’m not using this right now, but it’s good to know about for the future.

Third, you can call macros on library tokens from other macros. This is where it gets cool.

What is the MACRO roll option?

If you have any experience with programming other than in MapTool, you might be familiar with subroutines. Good programming often means that you’ll separate little pieces of code from one another and then “call” those other pieces of code when you need them. So, if you have a piece of code that sorts a column of data, let’s say, you’ll keep it separated and then “call” that code whenever you need to sort something.

In MapTool, you do this with the MACRO roll option. Note first of all that it is a roll option, not a function. This means that it comes BEFORE the colon within your square brackets of code, just like the “h” roll option for hiding your results. It also means that if you want to hide the results of the entire macro, you’ll format it as “[h, MACRO…” rather than “[h: MACRO…”.

I’ll note that there’s another way you can handle this, via User Defined Functions (UDF) in MapTool. Basically, you can use the defineFunction() function (wow, that sounds weird) to more or less make one of these called macro roll options into a function rather than a roll option, but that’s a topic for another time.

How does it work?

Let’s say I’ve created a macro on my Lib:LibToken1 library token, and I’ve called it ToggleState. This macro will let me toggle a condition (like Dazed) on or off of another token. I need to tell the ToggleState macro what state I’m toggling (Dazed), which is called an argument.

If I want to call the ToggleState macro, passing it the Dazed argument, it will look something like this:

[MACRO("ToggleState@Lib:LibToken1"): "Dazed"]

The roll option itself (MACRO) comes first. Within parentheses, I tell MapTool the name of the macro I’m calling (ToggleState) and where it can find that macro (on the token called lib:LibToken1) with an @ symbol in between. I then close the parentheses and insert a colon, followed by the argument (“Dazed”).

If you want to call a macro without any argument, you still need to include “” as a dummy argument. Without that, MapTool won’t execute the call.

If I want a bunch of different arguments, I can pass them as a string list or – you knew this was coming – a JSON object or array.

How do you use passed arguments?

Now let’s look at a piece of the macro I’ve called – the ToggleState macro.

[h: StateName=macro.args]

This line of code is accessing the argument I passed to the macro; StateName is set equal to the value of the special variable macro.args, which in this case is equal to “Dazed” (because that’s the value of the argument I passed to the macro). From there, the macro can continue to work, knowing the name of the state to toggle.

How do you pass information back from the called macro?

This is lovely and all, but it will often be useful for the macro I’ve called (ToggleStates) to pass some information BACK to the original macro. In order to do that, I use the special variable macro.return.

[h: macro.return="Toggle successful"]

I can include this line near the end of the called macro (ToggleStates). This sets the value of the special variable macro.return to “Toggle successful”. I can then access this information back in the original Dazed macro:

[h: DeliveredMessage=macro.return]
[r: DeliveredMessage]

Naturally, you can also pass back lists of things, often in a JSON object. So, from the called macro:

[h: ValuesToReturn=json.set('{ }', "Message", "Toggle successful", "NumTokensToggled", 3)]
[h: macro.return=ValuesToReturn]

And then in the calling macro:

[h: ReturnedValues=macro.return]
[h: MessageToDisplay=json.get(ReturnedValues, "Message")]
[h: NumTokensAffected=json.get(ReturnedValues, "NumTokensToggled")]
[MessageToDisplay] on [NumTokensAffected] tokens.

Bringing it all together

Now you know about the advanced tools I had to learn in order to write my state-tracking macro:

The final step: Showing you how it all comes together! Stay tuned for the exciting conclusion.

-Michael the OnlineDM

Advanced MapTool macros part 2: Intro to JSON arrays

As I mentioned in the first post in this series, I’ve been working on a set of macros to help me better track conditions on characters for D&D 4th Edition in MapTool. Building those macros has led me deeper into some of the capabilities of the MapTool macro language that I had previously avoided. Part 1 of the series focused on JSON objects; today we talk about JSON arrays.

What is a JSON array?

In any context, an array is basically a list. “1, 2, 3” is an array. A shopping list is an array.

A JSON array is a list enclosed in brackets and separated by commas for use in MapTool. As for what it’s a list OF, well, JSON arrays are flexible on that point.

You can have a JSON array that’s a list of numbers:

[r: MyJSONArray='[12, 3, -44]' ]

You can have a JSON array that’s a list of words:

[r: MyJSONArray='["Red", "Purple", "Yellow"]' ]

You can have a JSON array that’s a mixture of numbers and words:

[r: MyJSONArray='["Red", 3, -44]' ]

More interestingly, you can have a JSON array that’s a list of JSON objects – or even a list of other JSON arrays!

JSONObject1=[r: JsonObject1= '{"Color":"Red", "Number":12}' ]<br>
JSONObject2=[r: JsonObject2= '{"Color":"Purple", "Number":3}' ]<br>
JSONObject3=[r: JsonObject3= '{"Color":"Yellow", "Number":-44}' ]<br>
[h: MyJSONArray=' [ ]' ]
Array step 1=[r: MyJSONArray=json.append(MyJSONArray, JSONObject1) ]<br>
Array step 2=[r: MyJSONArray=json.append(MyJSONArray, JSONObject2) ]<br>
Array complete=[r: MyJSONArray=json.append(MyJSONArray, JSONObject3) ]<br>
<br>Second item = [r: SecondItem=json.get(MyJSONArray, 1)]
<br>Color of second item = [r: SecondItemColor=json.get(SecondItem, "Color")]

How do you build a JSON array?

As you can see from the examples above, you can build a simple JSON array in a manner very similar to building a JSON object. The contents of the array are separated by commas and enclosed in square brackets [ ], and the whole thing is enclosed in single quotes so that MapTool will allow you to use double quotes inside the array (so that it can contain words).

Another option I showed was to use the json.append function in MapTool. You need to start with an existing or blank JSON array (hence the MyJSONArray = ‘[ ]’ bit, creating a blank JSON array). You then append the new item to the end of the array.

What do you do with a JSON array?

As with a JSON object, a JSON array is useful if you store it as a property on a library token and then have it available for other macros to use later. It’s also useful because it’s an array – a list of things. With a list, you can do things like loop through each thing on the list using a FOREACH loop and do something to each thing on the list.

If you want to get some information out of an array, the simplest way to do it is to use the json.get command along with the appropriate index in the array. Each thing in the array has an index number, but you’ll want to keep in mind that the indexes start from zero.

Let me repeat that: The first item in an array is item 0, followed by item 1, and so on.

So, going back to my second array example, if I want to know what the middle of the three elements is, I’d do the following:

[r: MyJSONArray='["Red", "Purple", "Yellow"]' ]<br>
The middle color is [r: json.get(MyJSONArray, 1)]

Which returns:

[“Red”, “Purple”, “Yellow”]
The middle color is Purple

You’ll note that picked index 1 in my json.get statement. If I’d wanted it to say “Red” (the first element of the list), I would have asked for json.get(MyJSONArray, 0). Very important to remember.

If I want to know how many items are in the JSON array, I can use json.length. Keep in mind that my three-item array with have json.length=3, but the highest index number will be 2 (because it starts counting from zero). Confusing, yes.

How do I change what’s in a JSON array?

Technically speaking, you can’t edit a JSON array. However, you can re-save a new array with the same name that’s based off an existing array.

If you have a particular element of the array that you want to set to a new value, you can use the json.set command.

Original array: [r: MyJSONArray='["Red", "Purple", "Yellow"]' ]<br>
I'm changing the array now... [h: MyJSONArray=json.set(MyJSONArray, 1, "Green")]<br>
New array: [r: MyJSONArray]

This returns:

Original array: [“Red”, “Purple”, “Yellow”]
I’m changing the array now…
New array: [“Red”,”Green”,”Yellow”]

What I’ve done is created a new array that is just like the original array except that I’ve set the second element (index 1 is the second element, remember) to the value “Green” instead of whatever it was before. I’ve saved this new array with the SAME NAME as the original array. So, I’ve effectively changed the original array, but I’ve technically created a new array with the same name (which accomplishes the same thing).

Another useful command is json.remove, which gives you the array minus whatever you’ve removed.

Original array: [r: MyJSONArray='["Red", "Purple", "Yellow"]' ]<br>
If I remove the middle element, I get this new array: [r: NewJSONArray=json.remove(MyJSONArray, 1)]

This returns:

Original array: [“Red”, “Purple”, “Yellow”]
If I remove the middle element, I get this new array: [“Red”,”Yellow”]

If I’d used MyJSONArray= instead of NewJSONArray= in the second line, I would have effectively edited the array to remove the second element (index 1).

JSON objects within JSON arrays

Now, keep in mind that you can get fancy with JSON arrays. The things in the array can themselves be JSON objects or, messier still, JSON arrays. My last example in the opening section shows you one of these. It can get a little confusing depending on how deep you want to go, but there are definitely times when it’s useful to have lists of pairs of things (like colors with numbers, or token names with conditions).

Comparing JSON objects and JSON arrays

Bringing it all together, what are these things all about?

JSON objects are enclosed in curly braces { }. JSON arrays are enclosed in square brackets [ ].

JSON objects consist of labels and values (Name: Bob. Age: 23). JSON arrays are just lists of things (Bob, 23). However, you can put objects within arrays. You can also put objects within objects or arrays within objects or arrays within arrays… it can get messy!

To get a value out of an object, you can use json.get and then specify which key you want (PersonName=json.get(MyJSONObject, “Name”)). To get a value out of an array, you can use json.get and then specify the index of the item in the array that you want, with the first item having an index of 0 (PersonName=json.get(MyJSONArray, 0)).

Next step

So what’s next? Well, I keep talking about storing these objects and arrays on library tokens, so I think it’s time I talked about what a library token is.

Advanced MapTool macros part 1: Intro to JSON objects

Over the past few weeks, I’ve been working on what I expected would be a not-too-hard MapTool macro project. I wanted to create a macro (or set of macros, as it turned out) for putting conditions on tokens that would also keep track of when those conditions needed to end. Seemed simple enough; after all, I already had nice little macros that would toggle a condition on or off of a selected token or group of tokens. Those looked like this:

[h: SelectedTokens=getSelected()]
[FOREACH(TokenID, SelectedTokens, " "), CODE:
 {[h: NewState=if(getState("Dazed",TokenID)==1,0,1)]
 [h: setState("Dazed",NewState,TokenID)]
 }
]

However, in figuring out how to handle the automatic tracking of the condition end time, I had to delve deeper, trying to avoid Balrogs as I went.

Yes, dear readers: I had to learn about JSON! (Cue horrified screaming)

JSON stands for JavaScript Object Notation, and it’s something I had seen used by the REAL heavy lifters among MapTool macro writers. They had JSONs all over the place in their campaigns, and I tried my best to avoid them. They looked… intimidating.

I’ll warn you all right now, if you’re just looking for casual stuff about MapTool, skip this series of posts. This is heavy-duty macro programming compared to what I’ve written before. It still pales in comparison to the stuff written by the folks who create full-on MapTool frameworks, but it’s more than I’ve done in the past. However, if you need to learn about using JSONs, I hope to present it in an easy-to-follow manner.

What is a JSON object?

There are types of JSON in MapTool: JSON objects and JSON arrays. I’m going to talk about JSON objects today.

A JSON object is a single variable or property enclosed in braces {} that can hold several pieces of information.

For instance, here is a JSON object I ended up creating for my state-tracking macros:

{"TokenName":"Brute 1","State":"Cursed","EndCount":2,"EndRound":1,"AutoEnd":"1"}

This single object contains 5 pieces of information: The TokenName (Brute 1), the State on that token (Cursed), the count in the initiative order when that state will end (2), the round in which it will end (2) and a variable that indicates whether I want the condition to simply end automatically (1 means “yes” in this case) or not.

How do you build a JSON object?

If I wanted to build the object above, I could do it by assigning as a regular variable in MapTool, enclosing the whole thing in single quotes:

[h: MyJSONObject='{"TokenName":"Brute 1","State":"Cursed","EndCount":2,"EndRound":1,"AutoEnd":"1"}']

I could also use the ADD function if I had already created separate variables for, say, TokenName and State:

[h: CurrentTokenName="Brute 1"]
[h: CurrentState="Cursed"]
[h: MyJSONObject=add('{"TokenName":"', Brute 1, '","State":"', Cursed, '","EndCount":2,"EndRound":1,"AutoEnd":"1"}']

There’s also the option of using some of MapTool’s JSON functions, such as json.set:

[h: MyJSONObject=json.set("{ }", "TokenName", CurrentTokenName, "State", CurrentState, "EndCount", 2, "EndRound", 1, "AutoEnd", 1)]

Note that there are no single quotes needed with json.set. I start with the empty braces in quotes to let MapTool know that this will be a JSON object rather than a JSON array. I then list the key/value pairs, separated by commas.

Getting information from JSON objects

Once I’ve created this object, what the heck do I do with it? Well, assuming I save it as the value of a property of some token, I can have other macros refer to this property later to get information out of it. I do this with MapTool’s json.get function.

First, let’s say I have a library token called lib:LibToken1. Great name, right? And let’s say it has a property called StateObject, which starts off being set to 0. Now let’s assume that I assign my new JSON object to this property as follows:

[h: setProperty("StateObject", MyJSONObject, "lib:LibToken1")]

So, I set the StateObject property of the token called lib:LibToken1 equal to MyJSONObject.

In a later macro I can retrieve this property from the token and extract the information:

[h: CurrentStateObject=getProperty("StateObject","lib:LibToken1")]
[h: CurrentTokenName=json.get(CurrentStateObject, "TokenName")]
[h: CurrentState=json.get(CurrentStateObject, "State")]

The first line gets the StateObject property from the lib:LibToken1 token (which, remember, is equal to whatever I put in MyJSONObject) and stores it as CurrentStateObject (so CurrentStateObject=MyJSONObject). Then I can extract the TokenName value from that object and store it as CurrentTokenName using json.get. I do the same for the State value. I could do the same for the EndCount, EndRound and AutoEnd values. And then I can do whatever I like with these values inside my macro.

Why would you use a JSON object?

This is the question that kept me away from learning about JSON stuff for so long. Why do I even need it?

In lots of cases, you don’t actually NEED to use JSON, but it can make life easier. Where I decided I finally needed to use it was when I wanted to have a single property on a token that could hold lots of different stuff (in this case, information about states on tokens and when they end). The JSON object above is an example (actually as a piece of a JSON array) that appears in the StatesArray property of a token I created specifically to hold this kind of thing. Without using JSON, I would need to create a whole bunch of different properties on the token – one each for TokenName, State, EndCount, EndRound and AutoEnd. I could do that, but it would be messier.

And if I wanted to have a varying number of these objects – one for each token and state in the game – I’d need to create a huge multitude of properties, and I’d have to set up enough of them in advance so that I’d never run out. If I have 20 minions each getting slowed by a controller’s blast, well, I’d better have 100 properties set up in advance. If they’re being slowed and blinded, now I need 200 properties.

There’s got to be an easier way, and there is: JSON arrays. That’s the next post!