My latest attack macro has the following output:
Which is clunky and spacious, but to my warped mind is informative and readable. It could be reduced significantly and potentially has the capacity to be modified to output an entire attack sequence, but that is not really compatible with the rules of dungeons and dragons...
When you click on the button, it opens the following dialogue:
The pink and green backrounded sections are examples of the menus when expanded. You basically choose from a list of all the known options...
This is part one of the macro:
[h:"***Stuff that you may wish to set***"]
[h:"***Change the name of the macro below to match the macro with the relevant data. I recommend [this macro name]Data so that you know which macro matches which data. Obviously having two macros with the same name would confuse this sort of thing and is a generally bad idea.***"]
[MACRO("4GreatData@TOKEN"):""]
[h:weapon_name=json.get(macro.return,"weapon_name")]
[h:damage_type=json.get(macro.return,"damage_type")][h:"***anything relevant to damage reduction***"]
[h:weapon_die_number=json.get(macro.return,"weapon_die_number")] [h:"***A 1d3 unarmed strike would have one die.***"]
[h:weapon_die_sides=json.get(macro.return,"weapon_die_sides")][h:"***The dice on a 1d3 unarmed strike would have three sides.***"]
[h:crit_multiplier=json.get(macro.return,"crit_multiplier")]
[h:crit_range=json.get(macro.return,"crit_range")][h:"***lowest die roll that threatens a critical hit***"]
[h:other_attack_modifiers=json.get(macro.return,"other_attack_modifiers")][h:"***things like masterwork and weapon focus that don't fit anywhere else***"]
[h:other_damage_modifiers=json.get(macro.return,"other_damage_modifiers")][h:"***once again, things like weapon specialisation and enhancement bonuses***"]
[h:attack_ability=json.get(macro.return,"attack_ability")]
[h:damage_ability=json.get(macro.return,"damage_ability")]
[h:ability_damage_multiplier=json.get(macro.return,"ability_damage_multiplier")]
[h:power_attack_multiplier=json.get(macro.return,"power_attack_multiplier")][h:"***combat expertise support disabled***"]
[h:power_attack=json.get(macro.return,"power_attack")]
[h:"combat expertise=json.get(macro.return,"")"]
[h:base_attack_bonus=json.get(macro.return,"base_attack_bonus")]
[h:"***the following are for things like sneak attack or shocking weapons that have separate damage types and/or don't multiply with a critical hit. If extra_dice=[quote]0[unquote] then it will be ignored. Extra dice from bane goes here.***"]
[h:extra_dice_0=json.get(macro.return,"extra_dice_0")]
[h:extra_type_0=json.get(macro.return,"extra_type_0")]
[h:extra_dice_1=json.get(macro.return,"extra_dice_1")]
[h:extra_type_1=json.get(macro.return,"extra_type_1")]
[h:extra_dice_2=json.get(macro.return,"extra_dice_2")]
[h:extra_type_2=json.get(macro.return,"extra_type_2")]
[h:extra_dice_3=json.get(macro.return,"extra_dice_3")]
[h:extra_type_3=json.get(macro.return,"extra_type_3")]
[h:extra_dice_4=json.get(macro.return,"extra_dice_4")]
[h:extra_type_4=json.get(macro.return,"extra_type_4")]
[h:"***Extra damage that occurs only on a critical hit. Please match identical damage types to identical numbers.***"]
[h:crit_dice_0=json.get(macro.return,"crit_dice_0")]
[h:crit_type_0=json.get(macro.return,"crit_type_0")]
[h:crit_dice_1=json.get(macro.return,"crit_dice_1")]
[h:crit_type_1=json.get(macro.return,"crit_type_1")]
[h:crit_dice_2=json.get(macro.return,"crit_dice_2")]
[h:crit_type_2=json.get(macro.return,"crit_type_2")]
[h:crit_dice_3=json.get(macro.return,"crit_dice_3")]
[h:crit_type_3=json.get(macro.return,"crit_type_3")]
[h:"***Conditional effects that multiply on a critical hit such as bane or favoured enemy. A setting of 0 will have the entry ignored. Enhancement bonuses from bane go here.***"]
[h:conditional_attack_0=json.get(macro.return,"conditional_attack_0")]
[h:conditional_damage_0=json.get(macro.return,"conditional_damage_0")]
[h:conditional_reason_0=json.get(macro.return,"conditional_reason_0")]
[h:conditional_attack_1=json.get(macro.return,"conditional_attack_1")]
[h:conditional_damage_1=json.get(macro.return,"conditional_damage_1")]
[h:conditional_reason_1=json.get(macro.return,"conditional_reason_1")]
[h:conditional_attack_2=json.get(macro.return,"conditional_attack_2")]
[h:conditional_damage_2=json.get(macro.return,"conditional_damage_2")]
[h:conditional_reason_2=json.get(macro.return,"conditional_reason_2")]
[h:conditional_attack_3=json.get(macro.return,"conditional_attack_3")]
[h:conditional_damage_3=json.get(macro.return,"conditional_damage_3")]
[h:conditional_reason_3=json.get(macro.return,"conditional_reason_3")]
[h:conditional_attack_4=json.get(macro.return,"conditional_attack_4")]
[h:conditional_damage_4=json.get(macro.return,"conditional_damage_4")]
[h:conditional_reason_4=json.get(macro.return,"conditional_reason_4")]
[h:conditional_attack_5=json.get(macro.return,"conditional_attack_5")]
[h:conditional_damage_5=json.get(macro.return,"conditional_damage_5")]
[h:conditional_reason_5=json.get(macro.return,"conditional_reason_5")]
[h:"***This is where it asks for more information every time it is run. If these get annoying, consider making separate macros that assume these details. Maybe one where victim=nearest enemy(with quotes) or temporary_attack_bonuses=2, temporary_damage_bonuses=0, and attack_number=0 for charge attacks***"]
[h:attack_list="0"]
[h,if(base_attack_bonus>5):attack_list=(attack_list+",5")]
[h,if(base_attack_bonus>10):attack_list=(attack_list+",10")]
[h,if(base_attack_bonus>15):attack_list=(attack_list+",15")]
[h:status=input(
"victim|"+getVisibleTokenNames()+"|Select target|LIST|VALUE=STRING",
"attack_number|"+attack_list+"|BAB lost from position in attack sequence|LIST|VALUE=NUMBER",
"temporary_ab|0|Temporary attack bonuses",
"temporary_damage|0|Temporary damage bonuses")]
[h:abort(status)]
[h:"***Obvious alternatives to the above, delete the relevant line above and remove the quotes below, after making a backup macro...***"]
[h:"victim=""obvious choice"][h:"***D.M. chooses your target according to their whims***"]
[h:"attack_number=0"] [h:"***Every attack is your main attack***"]
[h:"attack_number=1"] [h:"***This is a secondary attack macro and always at -5***"]
[h:"temporary_ab=2"][h:"***+2 to hit for charging or flanking, +2 is common..."]
[h:"temporary_damage=1"][h:"***The bard always wins initiative and sings that same boring +1 song...***"]
[h:"***Old input disabled, though it may be faster to use***"]
[h:"victim=Please_input_target"]
[h:"***Ask the user for the target of the attack***"]
[h:"temporary_attack_bonuses=Please_input_conditional_Attack_modifiers"]
[h:"***Ask the user for any temporary changes to attack rolls, such as flanking, bless spell, attacking while prone...***"]
[h:"temporary_damage_bonuses=Please_input_conditional_Damage_modifiers"]
[h:"***As above, but relevant to damage rolls, things like favoured enemy, prayer spell, smite...***"]
[h:"attack_number=(Please_input_attack_number__Start_at_zero)"]
[h:"***The multiple of the modifier to apply due to position in the progression, the first attack is the default of 0 so you can skip it quickly, second attack will be 1 and at -5 to hit...***"]
[h:"***a 3.0 monk would want to change this to *3.***"]
[h:"***Extrapolating unknown values***"]
[h:temp_ability=eval(attack_ability)]
[h:ability_ab=floor(temp_ability/2-5)]
[h:temp_ability=eval(damage_ability)]
[h:temp_ability=floor(temp_ability/2-5)]
[h,if(temp_ability>0):ability_damage=floor(temp_ability*ability_damage_multiplier);ability_damage=temp_ability]
[h:base_dice=weapon_die_number+"d"+weapon_die_sides]
[h:crit_dice=weapon_die_number*(crit_multiplier-1)+"d"+weapon_die_sides]
[h:base_attack=(base_attack_bonus-attack_number*5)]
[h,if(power_attack>=100),CODE:
{[h:PA_ab=0]
[h:PA_damage=(power_attack-100)*power_attack_multiplier]
[h:power_attack=power_attack-100]
};{[h:PA_ab=power_attack]
[h:PA_damage=power_attack*power_attack_multiplier]
}]
[h:"***The macro proper starts here, hopefully you won't need to worry about this***"]
[h:"***Quick reference for the bonuses so people can see if you remembered the penalty for shooting into melee or the bonus from their buff spell***"]
Temporary Modifiers: attack:[r:temporary_ab]; Damage: [r:temporary_damage]; Power Attack: [r:power_attack][h:"; Combat Expertise [r:combat_expertise]"].<br>
[h:"***Calling the attack, just descriptive text.***"]
[r,if(attack_number == 0):"First"][r,if(attack_number == 1):"Second"][r,if(attack_number == 2):"Third"][r,if(attack_number == 3):"Fourth"]
attack with [r:weapon_name].<br>
Attacking <b>[r:victim]</b> with <i>[r:damage_type]</i> damage:<br><br>
[h:"***The attack roll.***"]
Would hit 1d20([t:AttackRoll=1d20][r,if(AttackRoll==20):"<b>!!!</b>"]) + [t:bonus=base_attack+ability_ab+temporary_ab+other_attack_modifiers-PA_ab] = [t:AttackRoll+bonus]<b>AC</b>
[t,if(conditional_attack_0!=0),CODE:{<br><i>or [t:AttackRoll+bonus+conditional_attack_0] AC [r:conditional_reason_0]</i>};{}]
[t,if(conditional_attack_1!=0),CODE:{<br><i>or [t:AttackRoll+bonus+conditional_attack_1] AC [r:conditional_reason_1]</i>};{}]
[t,if(conditional_attack_2!=0),CODE:{<br><i>or [t:AttackRoll+bonus+conditional_attack_2] AC [r:conditional_reason_2]</i>};{}]
[t,if(conditional_attack_3!=0),CODE:{<br><i>or [t:AttackRoll+bonus+conditional_attack_3] AC [r:conditional_reason_3]</i>};{}]
[t,if(conditional_attack_4!=0),CODE:{<br><i>or [t:AttackRoll+bonus+conditional_attack_4] AC [r:conditional_reason_4]</i>};{}]
[t,if(conditional_attack_5!=0),CODE:{<br><i>or [t:AttackRoll+bonus+conditional_attack_5] AC [r:conditional_reason_5]</i>};{}]
[h:"***Critical hit confirmation roll, if any***"]
[h:CritRoll=0]
[r,if(AttackRoll>=crit_range),CODE:
{<br><br><b>Critical hit confirmation roll hits 1d20([t:CritRoll=1d20][r,if(CritRoll==20):"</b>!!!<b>"]) + [t:bonus] = [t:CritRoll+bonus]AC.
[t,if(conditional_attack_0!=0),CODE:{<br><i>or [t:CritRoll+bonus+conditional_attack_0] AC [r:conditional_reason_0]</i>};{}]
[t,if(conditional_attack_1!=0),CODE:{<br><i>or [t:CritRoll+bonus+conditional_attack_1] AC [r:conditional_reason_1]</i>};{}]
[t,if(conditional_attack_2!=0),CODE:{<br><i>or [t:CrittRoll+bonus+conditional_attack_2] AC [r:conditional_reason_2]</i>};{}]
[t,if(conditional_attack_3!=0),CODE:{<br><i>or [t:CritRoll+bonus+conditional_attack_3] AC [r:conditional_reason_3]</i>};{}]
[t,if(conditional_attack_4!=0),CODE:{<br><i>or [t:CritRoll+bonus+conditional_attack_4] AC [r:conditional_reason_4]</i>};{}]
[t,if(conditional_attack_5!=0),CODE:{<br><i>or [t:CritRoll+bonus+conditional_attack_5] AC [r:conditional_reason_5]</i>};{}]
</b>};{}]
[h:"***Damage roll***"]
<br><br>For [r:base_dice]([t:damage_roll=eval(base_dice)]) + [t:bonus=ability_damage+other_damage_modifiers+temporary_damage+PA_damage] = [t,if(damage_roll+bonus>=1):damage=damage_roll+bonus;damage=1][r,if(extra_dice_0!="0"||extra_dice_1!="0"||extra_dice_2!="0"||extra_dice_3!="0"||extra_dice_4!="0"):" <b>damage</b><br>"]
[h:"***Extra damage dice take a lot of checking***"]
[h:extra_damage_0=0]
[r,if(extra_dice_0!="0"),CODE:{ +[t:extra_damage_0=eval(extra_dice_0)][r:extra_type_0]};{}]
[h:extra_damage_1=0]
[r,if(extra_dice_1!="0"),CODE:{ +[t:extra_damage_1=eval(extra_dice_1)][r:extra_type_1]};{}]
[h:extra_damage_2=0]
[r,if(extra_dice_2!="0"),CODE:{ +[t:extra_damage_2=eval(extra_dice_2)][r:extra_type_2]};{}]
[h:extra_damage_3=0]
[r,if(extra_dice_3!="0"),CODE:{ +[t:extra_damage_3=eval(extra_dice_3)][r:extra_type_3]};{}]
[h:extra_damage_4=0]
[r,if(extra_dice_4!="0"),CODE:{ +[t:extra_damage_4=eval(extra_dice_4)][r:extra_type_4]};{}]
[r,if(extra_dice_0!="0"||extra_dice_1!="0"||extra_dice_2!="0"||extra_dice_3!="0"||extra_dice_4!="0"),CODE:{ = [t:damage+extra_damage_0+extra_damage_1+extra_damage_2+extra_damage_3+extra_damage_4]};{}]
<b>damage</b> if it hits.
[h:"***Critical hit damage***"]
[r,if(AttackRoll>=crit_range),CODE:
{<br><br><b>Or [r:weapon_die_number*crit_multiplier+"d"+weapon_die_sides]([t:damage_roll=(damage_roll+eval(crit_dice))]) + [t:bonus*crit_multiplier] = [t,if(damage_roll+bonus*crit_multiplier>=crit_multiplier):damage=damage_roll+bonus*crit_multiplier;damage=crit_multiplier][h:extra=0] [h,if(extra_dice_0!="0"||extra_dice_1!="0"||extra_dice_2!="0"||extra_dice_3!="0"||extra_dice_4!="0"):extra=1]
[r,if(extra==1||crit_dice_0!="0"||crit_dice_1!="0"||crit_dice_2!="0"||crit_dice_3!="0"):" damage<br>"]
};{}]
[h:"***The dreaded return of the extra damage dice!!!***"]
[r,if(AttackRoll>=crit_range&&extra_dice_0!="0"),CODE:{ +[t,if(extra_type_0==crit_type_0):extra_damage_0=extra_damage_0+eval(crit_dice_0+"+0");extra_damage_0][r:extra_type_0]};{}]
[r,if(AttackRoll>=crit_range&&extra_dice_1!="0"),CODE:{ +[t,if(extra_type_1==crit_type_1):extra_damage_1=extra_damage_1+eval(crit_dice_1+"+0");extra_damage_1][r:extra_type_1]};{}]
[r,if(AttackRoll>=crit_range&&extra_dice_2!="0"),CODE:{ +[t,if(extra_type_2==crit_type_2):extra_damage_2=extra_damage_2+eval(crit_dice_2+"+0");extra_damage_2][r:extra_type_2]};{}]
[r,if(AttackRoll>=crit_range&&extra_dice_3!="0"),CODE:{ +[t,if(extra_type_3==crit_type_3):extra_damage_3=extra_damage_3+eval(crit_dice_3+"+0");extra_damage_3][r:extra_type_3]};{}]
[h:crit_damage_0=0]
[h:crit_damage_1=0]
[h:crit_damage_2=0]
[h:crit_damage_3=0]
[r,if(AttackRoll>=crit_range),CODE:
{[r,if(extra_dice_4!="0"),CODE:{ +[t:extra_damage_4][r:extra_type_4]};{}]
[r,if(crit_dice_0!="0"&&extra_type_0!=crit_type_0),CODE:{ +[t:crit_damage_0=eval(crit_dice_0)][r:crit_type_0]};{}]
[r,if(crit_dice_1!="0"&&extra_type_1!=crit_type_1),CODE:{ +[t:crit_damage_1=eval(crit_dice_1)][r:crit_type_1]};{}]
[r,if(crit_dice_2!="0"&&extra_type_2!=crit_type_2),CODE:{ +[t:crit_damage_2=eval(crit_dice_2)][r:crit_type_2]};{}]
[r,if(crit_dice_3!="0"&&extra_type_3!=crit_type_3),CODE:{ +[t:crit_damage_3=eval(crit_dice_3)][r:crit_type_3]};{}]
[h,if(extra_dice_0!="0"||extra_dice_1!="0"||extra_dice_2!="0"||extra_dice_3!="0"||extra_dice_4!="0"):extra=1]
[r,if(extra==1||crit_dice_0!="0"||crit_dice_1!="0"||crit_dice_2!="0"||crit_dice_3!="0"),CODE:
{ = [t:damage+extra_damage_0+extra_damage_1+extra_damage_2+extra_damage_3+extra_damage_4+crit_damage_0+crit_damage_1+crit_damage_2+crit_damage_3]
};{}]
damage if the critical hit is confirmed.</b>
};{}]
[h:"***Checks if you rolled a 1 on an attack roll and mentions the failure. If your game doesn't have autofail on confirmations then this is still mostly harmless, but should be easy to change***"]
[r,if(AttackRoll==1||CritRoll==1):"<br> But the natural 1 would make that difficult."]
[t,if(conditional_damage_0!=0),CODE:{<br><i>Add [t:conditional_damage_0][r,if(AttackRoll>=crit_range),CODE:
{<b>(or [t:conditional_damage_0*crit_multiplier])</b>};{}] damage [r:conditional_reason_0]</i>};{}]
[t,if(conditional_damage_1!=0),CODE:{<br><i>Add [t:conditional_damage_1][r,if(AttackRoll>=crit_range),CODE:
{<b>(or [t:conditional_damage_1*crit_multiplier])</b>};{}] damage [r:conditional_reason_1]</i>};{}]
[t,if(conditional_damage_2!=0),CODE:{<br><i>Add [t:conditional_damage_2][r,if(AttackRoll>=crit_range),CODE:
{<b>(or [t:conditional_damage_2*crit_multiplier])</b>};{}] damage [r:conditional_reason_2]</i>};{}]
[t,if(conditional_damage_3!=0),CODE:{<br><i>Add [t:conditional_damage_3][r,if(AttackRoll>=crit_range),CODE:
{<b>(or [t:conditional_damage_3*crit_multiplier])</b>};{}] damage [r:conditional_reason_3]</i>};{}]
[t,if(conditional_damage_4!=0),CODE:{<br><i>Add [t:conditional_damage_4][r,if(AttackRoll>=crit_range),CODE:
{<b>(or [t:conditional_damage_4*crit_multiplier])</b>};{}] damage [r:conditional_reason_4]</i>};{}]
[t,if(conditional_damage_5!=0),CODE:{<br><i>Add [t:conditional_damage_5][r,if(AttackRoll>=crit_range),CODE:
{<b>(or [t:conditional_damage_5*crit_multiplier])</b>};{}] damage [r:conditional_reason_5]</i>};{}]
And part two, which is a different macro and needs a specific name so that the other macro can find it.
[h:"***Stuff that you may wish to set***"]
[h:weapon_name="+4 shocking adamantine greatsword"]
[h:damage_type="slashing, magic, adamantine"][h:"***anything relevant to damage reduction***"]
[h:weapon_die_number="2"] [h:"***A 1d3 unarmed strike would have one die.***"]
[h:weapon_die_sides="6"][h:"***The dice on a 1d3 unarmed strike would have three sides.***"]
[h:crit_multiplier=2]
[h:crit_range=19][h:"***lowest die roll that threatens a critical hit***"]
[h:other_attack_modifiers=4][h:"***things like masterwork and weapon focus that don't fit anywhere else***"]
[h:other_damage_modifiers=4][h:"***once again, things like weapon specialisation and enhancement bonuses***"]
[h:attack_ability="Strength"]
[h:damage_ability="Strength"]
[h:ability_damage_multiplier=1.5]
[h:power_attack_multiplier=2][h:"***combat expertise support disabled***"]
[h:power_attack=Defense]
[h:"combat expertise=memory01"]
[h:base_attack_bonus=13]
[h:"***the following are for things like sneak attack or shocking weapons that have separate damage types and/or don't multiply with a critical hit. If extra_dice=[quote]0[unquote] then it will be ignored. Extra dice from bane goes here.***"]
[h:extra_dice_0="0"]
[h:extra_type_0="fire"]
[h:extra_dice_1="0"]
[h:extra_type_1="cold"]
[h:extra_dice_2="1d6"]
[h:extra_type_2="electric"]
[h:extra_dice_3="0"]
[h:extra_type_3="physical"]
[h:extra_dice_4="0"]
[h:extra_type_4="sneak attack"]
[h:"***Extra damage that occurs only on a critical hit. Please match identical damage types to identical numbers.***"]
[h:crit_dice_0="0"]
[h:crit_type_0="fire"]
[h:crit_dice_1="0"]
[h:crit_type_1="cold"]
[h:crit_dice_2="0"]
[h:crit_type_2="electric"]
[h:crit_dice_3="0"]
[h:crit_type_3="sonic"]
[h:"***Conditional effects that multiply on a critical hit such as bane or favoured enemy. A setting of 0 will have the entry ignored. Enhancement bonuses from bane go here.***"]
[h:conditional_attack_0=0]
[h:conditional_damage_0=0]
[h:conditional_reason_0="if it is an Evil outsider"]
[h:conditional_attack_1=-1]
[h:conditional_damage_1=0]
[h:conditional_reason_1="if I am in direct sunlight"]
[h:conditional_attack_2=0]
[h:conditional_damage_2=0]
[h:conditional_reason_2=""]
[h:conditional_attack_3=0]
[h:conditional_damage_3=0]
[h:conditional_reason_3=""]
[h:conditional_attack_4=0]
[h:conditional_damage_4=0]
[h:conditional_reason_4=""]
[h:conditional_attack_5=0]
[h:conditional_damage_5=0]
[h:conditional_reason_5=""]
[h:macro.return=json.set("{}","weapon_name",weapon_name,"damage_type",damage_type,"weapon_die_number",weapon_die_number,"weapon_die_sides",weapon_die_sides,"crit_multiplier",crit_multiplier,"crit_range",crit_range,"other_attack_modifiers",other_attack_modifiers,"other_damage_modifiers",other_damage_modifiers,"attack_ability",attack_ability,"damage_ability",damage_ability,"ability_damage_multiplier",ability_damage_multiplier,"power_attack_multiplier",power_attack_multiplier,"power_attack",power_attack,"base_attack_bonus",base_attack_bonus)]
[h,for(i,0,5,1):macro.return=json.set(macro.return,"extra_dice_"+i,eval("extra_dice_"+i),"extra_type_"+i,eval("extra_type_"+i))]
[h,for(i,0,4,1):macro.return=json.set(macro.return,"crit_dice_"+i,eval("crit_dice_"+i),"crit_type_"+i,eval("crit_type_"+i))]
[h,for(i,0,6,1):macro.return=json.set(macro.return,"conditional_attack_"+i,eval("conditional_attack_"+i),"conditional_damage_"+i,eval("conditional_damage_"+i),"conditional_reason_"+i,eval("conditional_reason_"+i))]
The second part should be fairly readable and people should be able to edit in whatever they need to as their situation changes.
Be advised that the macro uses the 'defense' attribute on your character to store a value for power attack. There are alternatives, but they are unpleasant... The defense paramater is set by this macro:
[h,if(defense>=100):power_attack=defense-100;power_attack=defense]
[h:status=input("power_attack|"+power_attack+"|Power Attack",
"heedless_charge|0|Heedless Charge|CHECK")]
[h:abort(status)]
[h,if(Defense>=100):AC=(AC+2+Defense-100)]
[h:Defense=power_attack+heedless_charge*100]
Power Attack = [r:power_attack]
[r,if(heedless_charge==1):"<br>Heedless Charging!"]
[h,if(heedless_charge==1):AC=(AC-2-power_attack)]
which is set up to accommodate the heedless charge ability. I made this macro to be as robust for a changing character as I could, and it may still have bugs floating around. If you get an intermittent error message, mention it, as it may only happen when you score a critical hit, which would be bad for your rolling average...