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