Here is a update from Azhrei's previous post. It adds all of the feats currently supported by the FW as of B86 Core 3 and takes out the 9 Slam attacks that are not needed. When I have more time, I will fix the DR/ER/SR and maybe someone nice will find a way to add into the label name the classes and levels by class or monster HD and class levels.
Code: Select all
@@ @<b>PF Statblock2Token</b> [1.9]
@PROPS@ fontColor=white ; autoExecute=true ; fontSize=11 ; sortBy=99 ; color=black ; playerEditable=true ; applyToSelected=false ; group= ; tooltip= ; minWidth=173 ;
<!-- Statblock2Token v1.8
Changes: HD fixed, Resets token, Skills with space in the name except SleightOfHand
Macro takes a Pathfinder statblock as input and updates selected token with various stats.
Macro assumes Pathfinder properties in the selected token.
Statblock format should mirror PF bestiary format.
Not handled: SQ, Spells
*CURRENTLY UNUSED*
General concepts. In order to isolate sections of the statblock, we generate a regular expression that includes all typical keywords and use it when searching for the end of a statblock section. This will prevent a string from running off into the next section.
*CURRENTLY UNUSED*
-->
[H: ids = getSelected()]
[H: abort(if(ids == "", 0, 1))]
[H: keywords = "(XP|Init|Senses|"+
"DEFENSE|AC|hp|Fort|Ref|Will|Defensive Abilities|"+
"OFFENSE|Speed|Melee|Ranged|Special Attacks|Spell-Like Abilities|"+
"(\\S+) Spells Prepared|Domains|"+
"STATISTICS|Base (Atk|Attack)|CMB|CMD|Feats|Skills|"+
"Languages|Combat Gear|Other Gear|Boon)"]
<!-- TOKEN SETUP BEGIN -->
[H: status = input("statblock|Insert statblock here|Enter statblock|TEXT|WIDTH = 40")]
[H: abort(if(status < 1, 0, 1))]
[H: setPropertyType("Pathfinder")]
[H: propnames = getPropertyNames()]
[H, foreach(propname, propnames): resetProperty(propname) ]
[H: setGMNotes(statblock)]
[H: output = "Token Updated:<BR />"]
[H: id = strfind(statblock, "^(.+).*CR ")]
[H, IF(0 < getFindCount(id)), CODE:
{
[name = getGroup(id, 1, 1)]
[output = "Name: "+name+", "]
[Race = json.set(Race, "name", name)]
[setName(name)]
}]
<!-- TOKEN SETUP END -->
<!-- RACE BEGIN -->
<!-- Size, type, race, subtype -->
[H: id = strfind(statblock, "(Fine|Diminutive|Tiny|Small|Medium|Large|Huge|Gargantuan|Colossal).([a-zA-Z]+\\s?[a-zA-Z]*?)\\s?(\\((.+)\\))?.?Init")]
[H, IF(0 < getFindCount(id)), CODE:
{
[Size_txt = getGroup(id, 1, 1)]
[setSize(Size_txt)]
[sizeList = table( "SysVars", json.get( table( "SysVars", 0 ), "sizeList" ) ) ]
[sizeModList = "8, 4, 2, 1, 0, -1, -2, -4, -8"]
[Size = listFind(sizeList, Size_txt)]
[SizeM = listGet(sizeModList, Size)]
[type = upper(substring(getGroup(id, 1, 2), 0, 1))+substring(getGroup(id, 1, 2), 1)]
[Race = json.set(Race, "type", type)]
[subtypes = getGroup(id, 1, 4)]
[output = output+"Size: "+Size_txt+", type: "+type+", subtypes: "+subtypes+"<BR />"]
[subtypes = "['"+replace(subtypes, ", *", "', '")+"']"]
[Race = json.set(Race, "subtype", subtypes)]
}]
<!-- RACE END -->
<!-- SENSES BEGIN -->
[H: id = strfind(statblock, "Senses ([lL]ow-?light|[dD]arkvision)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[sighttype = upper(substring(getGroup(id, 1, 1), 0, 1))+substring(getGroup(id, 1, 1), 1)]
[sighttype = replace(sighttype, "-", "")]
[setSightType(sighttype)]
[setHasSight(1)]
[output = output+"Sighttype: "+sighttype+", "]
}]
<!-- SENSES END -->
<!-- REACH BEGIN -->
[H: id = strfind(statblock, "Reach ([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[Reach = getGroup(id, 1, 1)]
[output = output+"Reach: "+Reach+", "]
}]
<!-- REACH END -->
<!-- RESISTANCE BEGIN -->
[H: id = strfind(statblock, "DR.?([0-9]+)/(.+?)[; O]")]
[H, IF(0 < getFindCount(id)), CODE:
{
[DR = getGroup(id, 1, 1)+"/"+getGroup(id, 1, 2))]
[output = output+"DR: "+DR+", "]
}]
[H: id = strfind(statblock, "Resist (\\w+) (\\d+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[resist = getGroup(id, 1, 1)+" "+getGroup(id, 1, 2))]
[DR = DR+" Resist "+resist]
[output = output+"Resist: "+resist+", "]
}]
[H: id = strfind(statblock, "SR ([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[SR = getGroup(id, 1, 1)]
[SpecialQual = "SR "+SR]
[output = output+"SR: "+SR+", "]
}]
<!-- RESISTANCE BEGIN -->
<!-- STATS BEGIN -->
[H: id = strfind(statblock, "Str.?([0-9]+)(/[0-9]+)?.+?Dex.?([0-9]+)(/[0-9]+)?.+?Con.?([0-9]+)(/[0-9]+)?.+?Int.?([0-9]+)(/[0-9]+)?.+?Wis.?([0-9]+)(/[0-9]+)?.+?Cha.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[Strength = getGroup(id, 1, 1)]
[IF(Strength == ""): Strength = 10]
[Dexterity = getGroup(id, 1, 3)]
[IF(Dexterity == ""): Dexterity = 10]
[Constitution = getGroup(id, 1, 5)]
[IF(Constitution == ""): Constitution = 10]
[Intelligence = getGroup(id, 1, 7)]
[IF(Intelligence == ""): Intelligence = 10]
[Wisdom = getGroup(id, 1, 9)]
[IF(Wisdom == ""): Wisdom = 10]
[Charisma = getGroup(id, 1, 11)]
[IF(Charisma == ""): Charisma = 10]
[output = output+"Str: "+Strength+", Dex: "+Dexterity+", Con: "+Constitution+", Int: "+Intelligence+", Wis: "+Wisdom+", Cha: "+Charisma+"<BR />"]
}]
<!-- STATS END -->
<!-- SAVES BEGIN -->
[H: id = strfind(statblock, "Fort(itude)?.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[Fort = getGroup(id, 1, 2)-ConB]
[output = output+"Fort: "+Fort+", "]
}]
[H: id = strfind(statblock, "Ref(lex)?.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[Reflex = getGroup(id, 1, 2)-DexB]
[output = output+"Ref: "+Reflex+", "]
}]
[H: id = strfind(statblock, "Will.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[Will = getGroup(id, 1, 2)-WisB]
[output = output+"Will: "+Will+"<BR />"]
}]
<!-- SAVES END -->
[H: id = strfind(statblock, "hp ([0-9]+).+?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[HP = getGroup(id, 1, 1)]
[HPmax = "[R: max(Level, ( "+(HP-ConB*Level)+" + (ConB * Level) ) )]"]
[HD = getGroup(id, 1, 2)]
[Level = max(1, HD)]
[output = output+"HP: "+HP+", HD: "+HD+"<BR />"]
}]
[H: id = strfind(statblock, "Base (Atk|Attack)?(.?[0-9]+); ")]
[H, IF(0 < getFindCount(id)), CODE:
{
[BAB = getGroup(id, 1, 2)]
[output = output+"Base Atk: "+BAB+", "]
}]
<!-- AC BEGIN -->
[H: id = strfind(statblock, "(.?[0-9]+) natural")]
[H, IF(0 < getFindCount(id)): natural = getGroup(id, 1, 1); natural = 0]
[H: ArmorClass = setStrProp(ArmorClass, "Natural", natural)]
[H: id = strfind(statblock, "(.?[0-9]+) armor")]
[H, IF(0 < getFindCount(id)): armor = getGroup(id, 1, 1); armor = 0]
[H: ArmorClass = setStrProp(ArmorClass, "Armor", armor)]
[H: id = strfind(statblock, "(.?[0-9]+) deflection")]
[H, IF(0 < getFindCount(id)): deflect = getGroup(id, 1, 1); deflect = 0]
[H: ArmorClass = setStrProp(ArmorClass, "Deflection", deflect)]
[H: id = strfind(statblock, "(.?[0-9]+) dodge")]
[H, IF(0 < getFindCount(id)): dodge = getGroup(id, 1, 1); dodge = 0]
[H: ArmorClass = setStrProp(ArmorClass, "Dodge", dodge)]
[H: id = strfind(statblock, "(.?[0-9]+) shield")]
[H, IF(0 < getFindCount(id)): shield = getGroup(id, 1, 1); shield = 0]
[H: ArmorClass = setStrProp(ArmorClass, "Shield", shield)]
[H: id = strfind(statblock, "AC ([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[AC = getGroup(id, 1, 1)]
[tch = AC-armor-shield]
[FF = AC-dodge-DexB]
[CMD = 10+BAB+StrB+DexB-SizeM+dodge+deflect]
[CMDFF = CMD-DexB-dodge]
[AC = concat(AC, "/", tch, "/", FF, "/", CMD, "/", CMDFF)]
[output = output+"AC/tch/FF/CMD/CMDFF: "+AC+"<BR />"]
}]
<!-- AC END -->
<!-- MOVEMENT BEGIN -->
[H: id = strfind(statblock, "(Speed|Spd) ([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[spd = getGroup(id, 1, 2)]
[output = output+"Spd: "+spd]
[Speed = json.set(Speed, "base", spd)]
}]
[H: id = strfind(statblock, "(?i)fly.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[spd = getGroup(id, 1, 1)]
[output = output+", Fly: "+spd]
[Speed = json.set(Speed, "fly", spd)]
}]
[H: id = strfind(statblock, "(?i)swim.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[spd = getGroup(id, 1, 1)]
[output = output+", Swim: "+spd]
[Speed = json.set(Speed, "swim", spd)]
}]
[H: id = strfind(statblock, "(?i)burrow.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[spd = getGroup(id, 1, 1)]
[output = output+", Burrow: "+spd]
[Speed = json.set(Speed, "burrow", spd)]
}]
[H: id = strfind(statblock, "(?i)climb.?([0-9]+)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[spd = getGroup(id, 1, 1)]
[output = output+", Climb: "+spd]
[Speed = json.set(Speed, "climb", spd)]
}]
[H: output = output+"<BR />"]
<!-- MOVEMENT END -->
<!-- SKILLS BEGIN -->
[H: tSkillsJ = "[]"]
[H: output = output+"Skills: "]
[H, FOREACH(s, SkillsJ), CODE:
{
[skN = json.get(s, "name")]
[id = strfind(statblock, skN+"\\s*([+-]?[0-9]+)")]
[IF(0 < getFindCount(id)), CODE:
{
<!-- Remove spaces from skill name -->
[skN = replace(skN, " ", "")]
[skStat = getStrProp(SkillStat, skN)]
[skVal = getGroup(id, 1, 1)]
[output = output + skN + " " + skVal + ", "]
[s = json.set( s, "rank", skVal-eval(skStat))]
}]
[tSkillsJ = json.append(tSkillsJ, s)]
}]
[H: output = output+"<BR />"]
[H: SkillsJ = tSkillsJ]
<!-- SKILLS END -->
<!-- FEATS BEGIN -->
[H: output = output+"Feats: "]
[H: featJSON = json.append("",
json.append("", "(Improved Initiative)", "ImprovedInitiative"),
json.append("", "(Agile Maneuvers)", "AgileManeuvers"),
json.append("", "(Improved Bull Rush)", "ImprovedBullRush"),
json.append("", "(Improved Disarm)", "ImprovedDisarm"),
json.append("", "(Improved Grapple)", "ImprovedGrapple"),
json.append("", "(Improved Overrun)", "ImprovedOverrun"),
json.append("", "(Improved Sunder)", "ImprovedSunder"),
json.append("", "(Improved Trip)", "ImprovedTrip"),
json.append("", "(Point.?[Bb]lank.?[Ss]hot)", "PointBlankShot"),
json.append("", "(Many.?[Ss]hot)", "ManyShot"),
json.append("", "(Two.?Weapon Fighting)", "TwoWeaponFighting"),
json.append("", "(Improved.?[Tt]wo.?Weapon Fighting)", "ImprovedTwoWeaponFighting"),
json.append("", "(Greater.?[Tt]wo.?Weapon Fighting)", "GreaterTwoWeaponFighting"),
json.append("", "(Multi.?[Aa]ttack)", "MultiAttack"),
json.append("", "(Weapon Finesse)", "WeaponFinesse"),
json.append("", "(Critical.?[Ff]ocus)", "CriticalFocus"),
json.append("", "(Uncanny Dodge)", "UncannyDodge"),
json.append("", "(Improved.?[Tt]urning)", "ImprovedTurning"),
json.append("", "(Die.?[Hh]ard)", "DieHard"),
json.append("", "(Improved Channeling)", "ImprovedChanneling"),
json.append("", "(Vital.?[Ss]trike)", "VitalStrike"),
json.append("", "(Improved.?[Vv]ital.?[Ss]trike)", "ImprovedVitalStrike"),
json.append("", "(Greater.?[Vv]ital.?[Ss]trike)", "GreaterVitalStrike")
)]
[H, FOREACH(featPair, featJSON), CODE:
{
[id = strfind(statblock, json.get(featPair, 0)) ]
[propName = json.get(featPair, 1) ]
[IF(0 < getFindCount(id)), CODE:
{
[Feats = setStrProp(Feats, propName, 1) ]
[output = output + getGroup(id, 1, 1) + ", " ]
}; {
[Feats = setStrProp(Feats, propName, 0) ]
}]
}]
[H: output = output+"<BR />"]
<!-- FEATS END -->
<!-- SPECIAL ABILITIES BEGIN -->
[H: id = strfind(statblock, "Special Attacks (.+)(Statis|STATIS)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[SpecialATK = getGroup(id, 1, 1)]
[SpecialATK = trim(SpecialATK)]
[SpecialATK = substring(SpecialATK, 0, min(length(SpecialATK), 70))]
[output = output+"SA: "+SpecialATK+"<BR />"]
}; {
[SpecialATK = ""]
}]
<!-- SPECIAL ABILITIES END -->
<!-- ATTACKS BEGIN -->
[H: atkno = 0]
<!-- MELEE -->
<!-- The '(?i)' makes the expression case-insensitive -->
[H: id = strfind(statblock, "(?i)Melee (.+?)(Tactic|Face|Space|Reach|Special|Ranged|Stat)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[allmelee = getGroup(id, 1, 1)]
<!-- separate each attack option -->
[allmelee = stringToList(allmelee, "( (and|or) (Melee)?)|(Melee)", ":")]
[atkno = listCount(allmelee, ":")]
}]
<!-- Loop through each attack option and divide into weapons -->
[H: wpnarray = ""]
[H: wpnno = 0]
[H, FOR(i, 0, atkno, 1), CODE:
{
[atkstr = listget(allmelee, i, ":")]
[atkstr = stringToList(atkstr, "( and )|(, )", ":")]
[wpns = listCount(atkstr, ":")]
[FOR(j, 0, wpns, 1), CODE:
{
[WpnName = "Weapon" + wpnno]
[wpnarray = json.append(wpnarray, trim(listGet(atkstr, j, ":")))]
[x = eval(WpnName) ]
[IF(j>0):eval(WpnName + " = '" + replace(x, "Primary = \\d+", "Primary = 2")+ "'")]
<!-- Set attack options after first to secondary attacks -->
[eval(WpnName + " = '" + replace(x, "OHLight = \\d+", "OHLight = 2")+ "'")]
<!-- Default to not multiattack -->
[wpnno = wpnno +1]
}]
}]
<!-- RANGED -->
[H: atkno = 0]
[H: id = strfind(statblock, "(?i)Ranged (.+?)(Tac|Face|Space|Reach|Special|Ranged|Stat)")]
[H, IF(0 < getFindCount(id)), CODE:
{
[allranged = getGroup(id, 1, 1)]
<!-- separate each attack option -->
[allranged = stringToList(allranged, "(\\s?or (Ranged)?)|(Ranged)", ":")]
[atkno = listCount(allranged, ":")]
}]
<!-- Loop through each attack option and divide into weapons -->
[H, FOR(i, 0, atkno, 1), CODE:
{
[atkstr = listget(allranged, i, ":")]
[atkstr = stringToList(atkstr, "( and )|(, )", ":")]
[wpns = listCount(atkstr, ":")]
[FOR(j, 0, wpns, 1), CODE:
{
[WpnName = "Weapon" + wpnno]
[wpnarray = json.append(wpnarray, trim(listGet(atkstr, j, ":")))]
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "Ranged", 1)+ "'")]
[wpnno = wpnno +1]
}]
}]
<!-- extract info from each attack -->
[H: wpnno = 0]
[H, FOREACH(atkstr, wpnarray), CODE:
{
[atkstr = trim(atkstr)]
[id = strfind(atkstr, "([+-]\\d+)?(\\d+)?(\\d?\\s?[a-zA-Z ]+?) ([+-]\\d+)[/+\\-0-9\\s]*?\\((.+)\\)")]
[IF(0 < getFindCount(id)), CODE:
{
[WpnName = "Weapon" + wpnno]
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "Name", getGroup(id, 1, 3))+ "'")]
<!-- Quantity -->
[wquant = 1]
[wbonus = ""]
[IF(getGroup(id, 1, 1) == ""): wquant = getGroup(id, 1, 2); wbonus = "+ "+getGroup(id, 1, 1)]
[IF(getGroup(id, 1, 2) == ""): wquant = 1]
[IF(1<wquant):eval(WpnName + " = '" + setStrProp(eval(WpnName), "Quantity", wquant)+ "'")]
<!-- Manufactored ? -->
[id2 = strfind(atkstr, "(slam|bite|claw|gore|hoof|tentacle|wing|pincer|tail|sting|talon)")]
[IF(0 == getFindCount(id2)):eval(WpnName + " = '" + setStrProp(eval(WpnName), "Manufactured", 1)+ "'")]
<!-- Not a natural weapon -->
<!-- Attack bonus (assumes StrB for melee and DexB for ranged) -->
[watkbonus = getGroup(id, 1, 4)]
[Ranged = getStrProp(eval(WpnName), "Ranged")]
[IF(Ranged>0):watkbonus = watkbonus-BAB-DexB; watkbonus = watkbonus-BAB-StrB-SizeM]
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "AtkBonus", watkbonus)+ "'")]
<!-- CritMult-->
[wdam = getGroup(id, 1, 5)]
[CritMult = 2]
[id2 = strfind(wdam, "/[xX](\\d)")]
[IF(0 < getFindCount(id2)):CritMult = getGroup(id2, 1, 1)]
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "CritMult", CritMult)+ "'")]
<!-- CritRange-->
[CritRange = 20]
[id2 = strfind(wdam, "/(1[0-9])")]
[IF(0 < getFindCount(id2)):CritRange = getGroup(id2, 1, 1)]
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "CritRange", CritRange)+ "'")]
<!-- Damage -->
[Damage = "0d4 plus" + wdam]
[id2 = strfind(wdam, "(\\dd\\d+)[^p]*(plus .+)?")]
[IF(0 < getFindCount(id2)):Damage = getGroup(id2, 1, 1)+wbonus+" "+getGroup(id2, 1, 2)]
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "Damage", Damage)+ "'")]
<!-- Two handed ? -->
[eval(WpnName + " = '" + setStrProp(eval(WpnName), "TwoHanded", 0)+ "'")] <!-- Default to onehanded weapon -->
[id2 = strfind(atkstr, "(longspear|quarterstaff| spear|falchion|glaive|greataxe|greatclub|greatsword|guisarme|halberd|lance|ranseur|scythe|spiked chain|elven curve blade|dire flail|two-bladed sword|urgrosh|hooked hammer)")]
[IF(0 < getFindCount(id2)):eval(WpnName + " = '" + setStrProp(eval(WpnName), "TwoHanded", 1)+ "'")]
}]
[wpnno = wpnno +1]
}]
[H: output = output + "Weapons: " + wpnarray+"<BR />"]
<!--
Name = Slam ; 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 ;
ATTACKS END -->
[R, S, G: output]
!!