Code: Select all
<!-- Statblock2Token v1.9
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
-->
[H: ids = getSelected()]
[H: abort(if(ids == "", 0, 1))]
[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),CODE:
{
[resetProperty(propname)]
}]
[H: setGMNotes(statblock)]
[H: output="Token Updated:<br>"]
[H: id = strfind(statblock, "^(.+).*CR ")]
[H, IF(0<getFindCount(id)),CODE:
{
[name=trim(getGroup(id, 1, 1))]
[output=output+"Name: "+name+", "]
[Race = json.set(Race, "name", name)]
[setName( name)]
}]
<!-- Setting ECL to CR from statblock. For CR 1/3 & 1/6 we will set it to .338 & .1625 so XP calcs correctly.-->
[H: id = strfind(statblock, "(?<=[ |\\t]CR )([1-9]+)(/[1-9]+)?")]
[H, IF(0<getFindCount(id)),CODE:
{
[ECL=trim(getGroup(id, 1, 0))]
[output=output+"CR: "+ECL+", "]
[IF(ECL == "1/3"),CODE:{
[ECL=".338"]
}]
[IF(ECL == "1/6"),CODE:{
[ECL=".1625"]
}]
[Levels = setStrProp(Levels, "ECL", ECL)]
}]
[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)]
}]
[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+", "]
}]
[H: id = strfind(statblock, "Reach ([0-9]+)")]
[H, IF(0<getFindCount(id)),CODE:
{
[Reach=getGroup(id, 1, 1)]
[output=output+"Reach: "+Reach+", "]
}]
[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+", "]
}]
<!-- STATS BEGIN -->
[H: id = strfind(statblock, "Str.?([0-9]*).+?Dex.?([0-9]*).+?Con.?([0-9]*).+?Int.?([0-9]*).+?Wis.?([0-9]*).+?Cha.?([0-9]*)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Strength=getGroup(id, 1, 1)]
[IF(Strength==""):Strength=10]
[Dexterity=getGroup(id, 1, 2)]
[IF(Dexterity==""):Dexterity=10]
[Constitution=getGroup(id, 1, 3)]
[IF(Constitution==""):Constitution=10]
[Intelligence=getGroup(id, 1, 4)]
[IF(Intelligence==""):Intelligence=10]
[Wisdom=getGroup(id, 1, 5)]
[IF(Wisdom==""):Wisdom=10]
[Charisma=getGroup(id, 1, 6)]
[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 (.?[0-9]+)")]
[H, IF(0<getFindCount(id)),CODE:
{
[Fort=getGroup(id, 1, 1)-ConB]
[output=output+"Fort: "+fort+", "]
}]
[H: id = strfind(statblock, "Ref (.?[0-9]+)")]
[H, IF(0<getFindCount(id)),CODE:
{
[Reflex=getGroup(id, 1, 1)-DexB]
[output=output+"Ref: "+reflex+", "]
}]
[H: id = strfind(statblock, "Will (.?[0-9]+)")]
[H, IF(0<getFindCount(id)),CODE:
{
[Will=getGroup(id, 1, 1)-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.?(.?[0-9]+);")]
[H, IF(0<getFindCount(id)), CODE:
{
[BAB=getGroup(id, 1, 1)]
[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 -->
[H: id = strfind(statblock, "(Speed|Spd) ([0-9]+) ft")]
[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, "fly ([0-9]+) ft")]
[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, "swim ([0-9]+) ft")]
[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, "burrow ([0-9]+) ft")]
[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, "climb ([0-9]+) ft")]
[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>"]
<!-- SKILLS BEGIN -->
[H: tSkillsJ ="[]"]
[H: output=output+"Skills: "]
[H, FOREACH(s, SkillsJ), CODE:
{
[skN = json.get(s, "name")]
[id=0]
[id = strfind(statblock, skN+"\\s(.?[0-9]+)")]
[H, IF(0<getFindCount(id)), CODE:
{
[skN = replace(skN, " ", "")] <!-- Remove spaces from skill name -->
[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: id = strfind(statblock, "(Improved Initiative)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedInitiative",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedInitiative",0)]
}]
[H: id = strfind(statblock, "(Agile Maneuvers)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "AgileManeuvers",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "AgileManeuvers",0)]
}]
[H: id = strfind(statblock, "(Improved Bull Rush)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedBullRush",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedBullRush",0)]
}]
[H: id = strfind(statblock, "(Improved Disarm)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedDisarm",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedDisarm",0)]
}]
[H: id = strfind(statblock, "(Improved Grapple)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedGrapple",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedGrapple",0)]
}]
[H: id = strfind(statblock, "(Improved Overrun)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedOverrun",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedOverrun",0)]
}]
[H: id = strfind(statblock, "(Improved Sunder)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedSunder",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedSunder",0)]
}]
[H: id = strfind(statblock, "(Improved Trip)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedTrip",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedTrip",0)]
}]
[H: id = strfind(statblock, "(Improved Channeling)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "ImprovedChanneling",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "ImprovedChanneling",0)]
}]
[H: id = strfind(statblock, "(Two.?Weapon Fighting)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "TwoWeaponFighting",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "TwoWeaponFighting",0)]
}]
[H: id = strfind(statblock, "(Multi.?[aA]ttack)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "MultiAttack",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "MultiAttack",0)]
}]
[H: id = strfind(statblock, "(Weapon Finesse)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "WeaponFinesse",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "WeaponFinesse",0)]
}]
[H: id = strfind(statblock, "(Uncanny Dodge)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "UncannyDodge",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "UncannyDodge",0)]
}]
[H: id = strfind(statblock, "(Die.?[hH]ard)")]
[H, IF(0<getFindCount(id)), CODE:
{
[Feats=setStrProp(Feats, "DieHard",1)]
[H: output=output+getGroup(id, 1, 1)+", "]
};{
[Feats=setStrProp(Feats, "DieHard",0)]
}]
[H: output=output+"<br>"]
<!-- FEATS END -->
[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=""]
}]
<!-- ATTACKS BEGIN -->
<!-- Insert empty weapon strings -->
[H: wpnstr=getPropertyDefault( "Weapon0" )]
[H, c(7,""),CODE:
{
[wpn="Weapon"+roll.count]
[eval(wpn + " = '" + wpnstr+ "'")]
}]
[H: atkno = 0]
<!-- MELEE -->
<!-- The '(?i)' makes the expression case-insensitive -->
[H: id = strfind(statblock, "Melee (.+?)(?i)(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, "( 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, "Ranged (.+?)(Tac|Face|Space|Reach|Special|Ranged|STAT|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]