I have my first cut at the updated D&D 3.5 campaign file that I will be using to run on Friday. It includes:
Properties
States
Combat Macros
Skill Check Macro
Stat Check Macro
Health Macros
AFK Macro
etc.

D&D3.5 MT1.3b43
Generic Attack MacroSample Campaign File for MapTool 1.3.b43As a GM, MapTool can greatly speed up the mechanics of battle. But to do that, a reasonably sophisticated algorithm is needed for the attack macro. Many things need to be taken into account. Knizia.fan has enabled a nice way to extend properties that makes a generic attack macro much cleaner.
The generic attack macro I created reasonably handles the following:
* Up to 9 distinct attacks (worst case I could identify was 7 - Marilith Demon with 6 different weapons and a tail)
* Primary vs. Secondary vs. 2 Handed vs. Two-Weapon attacks (both attack mods and STR mods)
* Weapon Finesse
* STR bows
* Multiple tentacles
* Decaying attacks for BAB > 5 (multiple attacks per round)
* Special damage like flaming sword
* Criticals (and crit multipliers 2,3,4)
* Special damage criticals (Burst weapons)
* Melee vs. Ranged attacks
* Verbosely show all attack information and damage modifiers
This macro will not handle:
* Cascading damage weapons like the Arqebus
* Display of damage rolls
* Multiple attacks of off-hand weapon for improved/greater two-weapon fighting
* No Strength penalty on bows that are composite (PHB pg. 134)
Here is a sample verbose output:

This creature has a bite and claw/claw secondary attacks, and is weilding a Longsword +1 Flaming Burst as a primary weapon and gains attacks with it like a player character.
Note: Currently there is a limit on how much output a macro can generate, that is why you only see verbose information for 2 of the 3 attacks, if I included the 3rd the macro would blow up.
To use this macro, the following properties must exist:
Code:
Private:" ; "
SizeM:0
BAB:0
MultAtkMP:0
MultAtkM:-5
WeaponFinesse:0
MiscATK:0
MiscDMG:0
[---Stats/Saves---]
Strength:10
Dexterity:10
Constitution:10
Intelligence:10
Wisdom:10
*Charisma:10
Fort:0
Reflex:0
Will:0
[---Weapons---]
Weapon1:Name=NA ; Primary=1 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon2:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon3:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon4:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon5:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon6:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon7:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon8:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
Weapon9:Name=NA ; Primary=0 ; Quantity=1 ; Manufactured=0 ; AtkBonus=0 ; CritMult=2 ; CritRange=20 ; Damage=1d4 ; DmgMax=4 ; DmgExtra=0d6 ; DmgExtraCrit=0d10 ; DmgExtraName= ; DmgBonusCap=50 ; TwoHanded=0 ; Finesse=0 ; OHLight=0 ; Ranged=0 ;
HelpWeaponProperty:Primary=0-NotInUse 1-Primary 2-Secondary ; Quantity= [---Calculated---]
StrB:{floor((Strength-10)/2))}
Str2hB:{floor(floor((Strength-10)/2)+(max(0,floor((Strength-10)/4))))}
StrSecB:{floor((Strength-10)/4))}
DexB:{floor((Dexterity-10)/2))}
Before the macro can process, the base character information needs to be setup, and the various attack forms need to be identified. The attack forms (weapons) are edited with this dialog:

Which is generated by this macro:
Code:
============
Weapons Edit
============
<!-- Build the list of weapon names. -->
[h: WpnList = ""]
[h, c(9,""): WpnList = WpnList + getStrProp(eval("Weapon" + roll.count), "Name") + ","]
<!-- Ask the user to select one of the weapons. -->
[h: fail = input("WpnNum | " + WpnList + " | Select weapon to edit | LIST")]
[h: abort(fail)]
<!-- Obtain the property string for the selected weapon. -->
[h: WpnNum = WpnNum + 1]
[h: WpnName = "Weapon" + WpnNum]
[h: WpnProps = eval(WpnName)]
<!-- Error checking -- make sure the property string has been set up already. -->
[h: NumProps = countStrProp(eval(WpnName))]
[h: abort(NumProps)]
<!-- Put up a dialog with all the properties in the property string.
-- Note that the new property string is automatically assigned back to the
-- token property that holds the weapon's property string. -->
[h: NumHelpProps = countStrProp(HelpWeaponProperty)]
[h: HelpLabel = ""]
[h, c(NumHelpProps,""): eval("HelpLabel" + roll.count + " = 'junk" + roll.count + " | " + indexValueStrProp(HelpWeaponProperty,roll.count - 1) + " | " + indexKeyStrProp(HelpWeaponProperty,roll.count - 1) + " | LABEL")]
[h: fail = input("blah | " + WpnNum + " | Weapon number | LABEL", WpnName + " | " + WpnProps + " | Weapon properties | PROPS | setvars=true", HelpLabel1, HelpLabel2, HelpLabel3, HelpLabel4, HelpLabel5, HelpLabel6, HelpLabel7, HelpLabel8, HelpLabel9, HelpLabel10, HelpLabel11, HelpLabel12)]
[h: abort(fail)]
This is basically a copy of kniaia.fan's code with the exceptions of adding help information.
The core code of the attack macro is:
Code:
[H: FAN = floor(BAB/6) + 1]
[H: varsFromStrProp(Weapon1)]
[H: temp1 = ""]
[H: temp2 = ""]
[H: temp3 = ""]
[H: temp4 = ""]
[H: atkDecay = if((Primary == 1) && (Manufactured == 1), 1, 0)]
[H: numAtk = if(atkDecay==1, FAN, Quantity)]
[H: atkMult = If(OHLight==2, 0,If(Primary==1, min(0, max(MultAtkMP, MultAtkMP+(OHLight*2))), min(0, max(MultAtkM, MultAtkM+(OHLight*2)))))]
[H: atkStatB = if(Ranged==1, DexB, if(WeaponFinesse==1 && Finesse==1, max(StrB, DexB), StrB))]
[H: dmgStatB = min(DmgBonusCap, if(TwoHanded==1, Str2hB, if(Primary==2,StrSecB,StrB)))]
[H, C(numAtk, ""): eval("result1" + roll.count + "= 1d20")]
[H, C(numAtk, ""): eval("result1c" + roll.count + "= 1d20")]
[H, C(numAtk, ""): temp1 = temp1 + eval("eval('result1' + roll.count) + BAB -(atkDecay*5*(roll.count - 1)) + atkMult + SizeM + atkStatB + AtkBonus + MiscATK") + if(numAtk != roll.count, ", ", "")]
[H, C(numAtk, ""): temp2 = temp2 + if(eval("result1" + roll.count) >= CritRange, " <i>. . . CRIT! (roll " + roll.count + ") -> </i>" + eval("eval('result1c' + roll.count)+BAB-(atkDecay*5*(roll.count - 1))+atkMult+SizeM+atkStatB+AtkBonus+MiscATK"), ""))]
[H, C(numAtk, ""): temp3 = temp3
+ eval("eval(Damage) + dmgStatB + MiscDMG")
+ if(DmgExtra != "0d6", " + " + eval(DmgExtra) + " " + DmgExtraName, "")
+ if(numAtk != roll.count, ", "
, "")]
[H, C(numAtk, ""): temp4 = temp4
+ if(eval("result1" + roll.count) >= CritRange, " <i>. . . CRIT! (roll " + roll.count + ") -> </i>"
+ eval("eval(Damage) + dmgStatB + MiscDMG")
+ if(CritMult > 2, " + " + eval("eval(Damage) + dmgStatB + MiscDMG"),"")
+ if(CritMult > 3, " + " + eval("eval(Damage) + dmgStatB + MiscDMG"),"")
+ if(DmgExtraCrit != "0d10", " + " + eval("eval(DmgExtraCrit) * (CritMult - 1)") + " " + DmgExtraName, "")
, "")]
[H: AtkString1 = "<b>HIT:</b> (<i> " + Name + " </i>) " + temp1 + temp2 + "<br/><b>DMG:</b> (<i> " + Name + " </i>) " + temp3 + temp4 + "<br />"]
[H: atkList = ""]
[H,C(numAtk, ""): atkList = atkList + if(numAtk != 1, ",", "") + eval("result1" + roll.count)]
[H: atkList = "<font size= -2>Rolls:" + atkList + " BAB:" + BAB + " MultiAtk:" + atkMult + " Size:" + SizeM + " StatBonus:" + atkStatB + " AtkBonus:" + AtkBonus + " CritThreat:" + CritRange + " Misc:" + MiscATK + "</font><br />"]
[H: dmgList = "<font size= -2>DamageDice:" + Damage + " StatBonus:" + dmgStatB + " MiscDMG:" + MiscDMG + " ExtraDMG:" + DmgExtra + " ExtraDMGType:" + DmgExtraName + " CritDMG:" + DmgExtraCrit + " CritMultiplier:" + CritMult + "</font><br />"]
[P: If(Primary != 0, AtkString1, "")]
[P: If(Primary != 0, atkList, "")]
[P: If(Primary != 0, dmgList, "")]
To deal with multiple different attacks, you just repeat this code N times (I do 9 to cover all 9 potential weapon property entries) and renumber the Weapon in this line (oh and there is no need to repeat [H: FAN = floor(BAB/6) + 1] as that is a global variable):
Code:
[H: varsFromStrProp(Weapon2)]
For example, this will display 9 different attack forms. (Yeah, a looping structure would rock.)
Code:
[H: FAN = floor(BAB/6) + 1]
[H: varsFromStrProp(Weapon1)]
... core code ...
[H: varsFromStrProp(Weapon2)]
... core code ...
[H: varsFromStrProp(Weapon3)]
... core code ...
[H: varsFromStrProp(Weapon4)]
... core code ...
[H: varsFromStrProp(Weapon5)]
... core code ...
[H: varsFromStrProp(Weapon6)]
... core code ...
[H: varsFromStrProp(Weapon7)]
... core code ...
[H: varsFromStrProp(Weapon8)]
... core code ...
[H: varsFromStrProp(Weapon9)]
... core code ...
Now I will break down the code for clarification:
First we identify a number of properties about the being:
Size Modifier +1 small, 0 medium, -1 large, etc.
Base Attack Bonus (BAB)
MultAtkMP:0 For monsters these are default 0/-5 but with feat 0/-2
MultAtkM:-5 For PCs these are -6/-10 but with feat -4/-4
WeaponFinesse - Do they have this feat?
Strength
Dexterity
Player characters (and monsters that have a manufactured weapon in their first primary attack) get decaying attacks (multiple attacks per round) based on Base Attack Bonus/6 + 1. This first line calculates how many attacks that primary weapon would get.
Code:
[H: FAN = floor(BAB/6) + 1]
Next we fetch all of the information about the attack form (weapon):
Code:
[H: varsFromStrProp(Weapon1)]
Name=NA ; Text string descriptor
Primary=1 ; 0=NotInUse, 1=Primary, 2=Secondary
Quantity=1 ; # of Natural weapon attacks
Manufactured=0 ; 0=Natural, 1=Manufactured - only ONE weapon may be Primary+Manufactured, the code does not check.
AtkBonus=0 ; Magic/Masterwork/Feat bonuses go here
CritMult=2 ; 2/3/4 (4 is the max the code handles)
CritRange=20 ; Enter the lower end, so if range is 18-20, enter 18
Damage=1d4 ; Include magic and feat bonus damage here
DmgMax=4 ; For future use
DmgExtra=0d6 ; 0d6=null, must be a die roll entry as burps on fixed number
DmgExtraCrit=0d10 ; 0d10=null, must be a die roll entry as burps on fixed number
DmgExtraName= ; Text string descriptor like Fire
DmgBonusCap=50 ; Maximum bonus from strength (useful for things like +2 STR bow)
TwoHanded=0 ; 0=one handed, 1=two handed
Finesse=0 ; 0=not finesse able, 1=weapon finesse able
OHLight=0 ; 0=off hand heavy, 1= off hand light, 2=NOT a multi-attack weapon
Ranged=0 ; 0=melee, 1= ranged
Calculate if the weapon should get multiple attacks a round if the BAB is high. Note that when entering weapon information, you must only set ONE weapon as both Primary=1 and Manufactured=1. For example, a Marilith Demon gets 6 primary attacks and 1 secondary Tail attack. The Marilith could have 6 weapons in its 6 hands, but ONLY ONE of those gets the multiple attacks per round, while the other 5 count as primary attacks.
This is just a 0/1 multiplier variable that enables/disables the attack decay.
Code:
[H: atkDecay = if((Primary == 1) && (Manufactured == 1), 1, 0)]
Next we calculate the number of attacks for this weapon. If it gets decay attacks then we use the BAB to calculate the number of attacks. Otherwise we use the weapon property (this is useful for things like 2 claws, or 6 tentacles).
Code:
[H: numAtk = if(atkDecay==1, FAN, Quantity)]
Next we calculate the attack modifier for secondary and two-weapon attacks. Monsters by default are Primary:0 / Secondary:-5 but with the Multi-Attack feat the Secondary drops to -2. Players are Primary:-6 / Secondary:-10, but with Two-Weapon Fighting feat this drops to -4/-4. Also, if the off-hand weapon is light, that reduces the primary and secondary penalties by 2. If you use a macro that just references specific weapons then you can use OHLight=2 to signify this particular weapon should not get multi-attack penalties.
Code:
[H: atkMult = If(OHLight==2, 0,If(Primary==1, min(0, max(MultAtkMP, MultAtkMP+(OHLight*2))), min(0, max(MultAtkM, MultAtkM+(OHLight*2)))))]
Now we figure what stat bonus to use for attack. The variables in this are: 1) Is it a ranged weapon (if so use Dex for attack), 2) Is this weapon finesse able and does the character have the Weapon Finesse feat (if so, use the higher of STR or DEX), 3) otherwise, use Strength.
Code:
[H: atkStatB = if(Ranged==1, DexB, if(WeaponFinesse==1 && Finesse==1, max(StrB, DexB), StrB))]
In the same vein, we calculate what stat bonus to use for attack. This is always Strength but if Secondary then 1/2 STR, if 2-handed weapon or monster with only one (sole) attack then 1.5 * STR. Ranged weapons are controlled by data entry, just set the STR bonus cap for that weapon. That allows coverage for things like slings or +2 strength bows. Note that the variables used here are calculated properties.
Code:
[H: dmgStatB = min(DmgBonusCap, if(TwoHanded==1, Str2hB, if(Primary==2,StrSecB,StrB)))]
Finally we roll our attack and critical attack dice.
Code:
[H, C(numAtk, ""): eval("result1" + roll.count + "= 1d20")]
[H, C(numAtk, ""): eval("result1c" + roll.count + "= 1d20")]
Now we assemble our attack rolls, and if the die roll >= critical threat range, we calculate the critical roll too.
Code:
[H, C(numAtk, ""): temp1 = temp1 + eval("eval('result1' + roll.count) + BAB -(atkDecay*5*(roll.count - 1)) + atkMult + SizeM + atkStatB + AtkBonus + MiscATK") + if(numAtk != roll.count, ", ", "")]
[H, C(numAtk, ""): temp2 = temp2 + if(eval("result1" + roll.count) >= CritRange, " <i>. . . CRIT! (roll " + roll.count + ") -> </i>" + eval("eval('result1c' + roll.count)+BAB-(atkDecay*5*(roll.count - 1))+atkMult+SizeM+atkStatB+AtkBonus+MiscATK"), ""))]
Next we calculate the damage. I have the die rolls occuring directly in these calculations which is why they are not available for verbose output and why cascading damage weapons are not supported. Also, I calculate critical damage through an IF tree so have limited the critical multiplier to 4 (which is the cap in the PHB). Criticals do not add special damage (like from a flaming sword), but will add special critical damage (like from a burst sword). Special critical damage increases if the critical multiplier is > 2 (as per the DMG).
Code:
[H, C(numAtk, ""): temp3 = temp3
+ eval("eval(Damage) + dmgStatB + MiscDMG")
+ if(DmgExtra != "0d6", " + " + eval(DmgExtra) + " " + DmgExtraName, "")
+ if(numAtk != roll.count, ", "
, "")]
[H, C(numAtk, ""): temp4 = temp4
+ if(eval("result1" + roll.count) >= CritRange, " <i>. . . CRIT! (roll " + roll.count + ") -> </i>"
+ eval("eval(Damage) + dmgStatB + MiscDMG")
+ if(CritMult > 2, " + " + eval("eval(Damage) + dmgStatB + MiscDMG"),"")
+ if(CritMult > 3, " + " + eval("eval(Damage) + dmgStatB + MiscDMG"),"")
+ if(DmgExtraCrit != "0d10", " + " + eval("eval(DmgExtraCrit) * (CritMult - 1)") + " " + DmgExtraName, "")
, "")]
Then I put the attack and damage together in a string.
Code:
[H: AtkString1 = "<b>HIT:</b> (<i> " + Name + " </i>) " + temp1 + temp2 + "<br/><b>DMG:</b> (<i> " + Name + " </i>) " + temp3 + temp4 + "<br />"]
Then I assemble the various elements and generate the verbose data strings. Note that currently macro output quantity is limited, and this extra data will cause the macro to crash.
Code:
[H: atkList = ""]
[H,C(numAtk, ""): atkList = atkList + if(numAtk != 1, ",", "") + eval("result1" + roll.count)]
[H: atkList = "<font size= -2>Rolls:" + atkList + " BAB:" + BAB + " MultiAtk:" + atkMult + " Size:" + SizeM + " StatBonus:" + atkStatB + " AtkBonus:" + AtkBonus + " CritThreat:" + CritRange + " Misc:" + MiscATK + "</font><br />"]
[H: dmgList = "<font size= -2>DamageDice:" + Damage + " StatBonus:" + dmgStatB + " MiscDMG:" + MiscDMG + " ExtraDMG:" + DmgExtra + " ExtraDMGType:" + DmgExtraName + " CritDMG:" + DmgExtraCrit + " CritMultiplier:" + CritMult + "</font><br />"]
And finally we output the results only if the weapon is actually in use (Primary != 0).
Code:
[P: If(Primary != 0, AtkString1, "")]
[P: If(Primary != 0, atkList, "")]
[P: If(Primary != 0, dmgList, "")]