D&D 5e Statblock Importer.

Discuss macro implementations, ask for macro help (to share your creations, see User Creations, probably either Campaign Frameworks or Drop-in Resources).

Moderators: dorpond, trevor, Azhrei, giliath, jay, Mr.Ice

User avatar
aliasmask
RPTools Team
Posts: 9031
Joined: Tue Nov 10, 2009 6:11 pm
Location: California

Re: D&D 5e Statblock Importer.

Post by aliasmask »

I recall this from another user asking the same thing. Check out this thread: viewtopic.php?f=20&t=27448&p=272028&hil ... ck#p272029

User avatar
Sereptus
Giant
Posts: 123
Joined: Tue Jun 07, 2011 12:08 pm
Location: Canada

Re: D&D 5e Statblock Importer.

Post by Sereptus »

I have been away from that code for a while, since we went back to Pathfinder, although a I recall it as working. I'll do some tests as soon as I have time. I've come a long way in my programming/ RegEx since doing that importer. If there's an interest I'll take it up again and clean up a bunch of the code and and provide the token properties necessary for the importer.
OOOHH RegEx....YOU BITTER-SWEET BEAST!!!

User avatar
PinkRose
Dragon
Posts: 732
Joined: Sat Nov 15, 2008 2:28 pm
Location: The City of Roses, Oregon

Re: D&D 5e Statblock Importer.

Post by PinkRose »

I would definitely take a Beyond to Maptool converter.
I tried the one that AliasMask linked above that you wrote.
It's missing a lib:CharacterSheet and the macros call for global macros.
I would happilly take a copy of any 5e Campaign you have if that's easier.
I am a special snowflake!

User avatar
Sereptus
Giant
Posts: 123
Joined: Tue Jun 07, 2011 12:08 pm
Location: Canada

Re: D&D 5e Statblock Importer.

Post by Sereptus »

PinkRose wrote:
Mon Aug 12, 2019 2:42 pm
I would definitely take a Beyond to Maptool converter.
It also should work for PDFs and other formats, not just D&D Beyond.

PinkRose wrote:
Mon Aug 12, 2019 2:42 pm
I would happilly take a copy of any 5e Campaign you have if that's easier.
Ok, I'll get that to you ASAP. It's still a WIP but you probably won't have too much trouble with it if you've been using Maptool for any length of time. I just didn't have time to clean it up and finish it but you can sort through it and take what you like. I'll PM you the link to the campaign file as I'm not sure I want to share unfinished works here on the forums as I'm sure I'd get a lot more help requests! :P

Cheers
OOOHH RegEx....YOU BITTER-SWEET BEAST!!!

User avatar
PinkRose
Dragon
Posts: 732
Joined: Sat Nov 15, 2008 2:28 pm
Location: The City of Roses, Oregon

Re: D&D 5e Statblock Importer.

Post by PinkRose »

Any progress on this?
I am a special snowflake!

User avatar
Sereptus
Giant
Posts: 123
Joined: Tue Jun 07, 2011 12:08 pm
Location: Canada

Re: D&D 5e Statblock Importer.

Post by Sereptus »

Inboxed you the campaign file.
OOOHH RegEx....YOU BITTER-SWEET BEAST!!!

pixel7777
Kobold
Posts: 9
Joined: Wed Nov 06, 2019 1:34 pm

Re: D&D 5e Statblock Importer.

Post by pixel7777 »

Hi - so I tried this today, and I keep getting an error for "Stats in statblock not found." I tried it with both a PDF and DnD Beyond stat block, and I made sure that the campaign properties were as specified. Does this work with the most recent version of MapTool (I just updated to 1.5.14 today)?

I'm really interested in getting this working. I like buying affordable adventures from DriveThruRPG and then running the module in MapTool for in-person game play (VTT to a TV laid flat with minis on top). Previously I just put the maps into MapTool and ran initiative/HP on a piece of paper and kept the monster stat blocks in a variety of places, but I'm getting pretty tired of doing it that way. I'm hoping to have the monster stats available through the token and add some macros to do simple initiative and HP.

But if I have to manually build the stat blocks in MapTool from the typical PDFs provided by adventure authors, I'll may be just suck it up and go with the old system ;)

User avatar
Sereptus
Giant
Posts: 123
Joined: Tue Jun 07, 2011 12:08 pm
Location: Canada

Re: D&D 5e Statblock Importer.

Post by Sereptus »

I'll take a look. Haven't tested it on the new Maptool yet.
OOOHH RegEx....YOU BITTER-SWEET BEAST!!!

pixel7777
Kobold
Posts: 9
Joined: Wed Nov 06, 2019 1:34 pm

Re: D&D 5e Statblock Importer.

Post by pixel7777 »

Sereptus wrote:
Thu Mar 05, 2020 5:52 am
I'll take a look. Haven't tested it on the new Maptool yet.
Thanks! Just to be clear, under campaign properties, should I have edited Basic to be 5E-NPC or have created a new token type 5E-NPC? (I did the second)

User avatar
Sereptus
Giant
Posts: 123
Joined: Tue Jun 07, 2011 12:08 pm
Location: Canada

Re: D&D 5e Statblock Importer.

Post by Sereptus »

Yeah second one is fine. Downloading new Maptool now and will test soon.
OOOHH RegEx....YOU BITTER-SWEET BEAST!!!

XKnives
Kobold
Posts: 1
Joined: Fri Apr 10, 2020 5:13 am

Re: D&D 5e Statblock Importer.

Post by XKnives »

I believe I mended the issue causing 'Stats in statblock not found.' assert to go off.
It was a minor change, I removed the 2nd regex matching.

Code: Select all

[H: ids = getSelected()]
[H: abort(if(ids == "", 0, 1))]



[H: status=input("junk|Statblock info from Creature Name through Treasure line of Ecology (if it exists). Don't include flavor text/background/descriptions.||LABEL|SPAN=TRUE","junk|If Ecology section comes before Special Abilities, be sure to include Special Abilities section as well. Still no flavor text.||LABEL|SPAN=TRUE","statblock|Insert 5E statblock here|Enter statblock|TEXT|WIDTH=40")]
[H: abort(if(status < 1, 0, 1))]

[H: CRLF = decode("%0D%0A")]

[H: setPropertyType("5E-NPC")]
[H: GP=getProperty("dnd.weapons")]
[H: propnames = getPropertyNames()]
[H, foreach(propname,propnames),CODE: {
   [resetProperty(propname)]
}]

[H: setProperty("dnd.weapons", GP)]


[H: '<!-- Lets clean up those pesky non-ascii characters! -->']
[H: statblock = replace(statblock, "\\xD7", "x")]
[H: statblock = replace(statblock, "\\u2013", "-")]
[H: statblock = replace(statblock, "\\u2014", "-")]
[H: statblock = replace(statblock, "%E2%80%93", "-")]
[H: statblock = replace(statblock, "\\u2212", "-")]

[H: statblock = replace(statblock, "  ", " ")]

[H: '<!-- change brackets and braces to parens to avoid stat sheet variable input error -->']
[H: statblock = replace(statblock, "[\\[\\{]","(")]
[H: statblock = replace(statblock, "[\\]\\}]",")")]

[H: '<!-- Start formatting imported statblock and set it to GM notes -->']



[H: '<!-- Search for lines and sections of statblock individually -->']

[H: NameSearch = strfind(statblock, "(.*?)(CoS|OotA|SKT|PotA|ToD|VGM|MM|Tiny|Small|Medium|Large|Huge|Gargantuan)")]
[H: THEName= getGroup(NameSearch, 1, 1)]
[H: NameSearch=trim(THEName)]

[H: setName(trim(THEName))]


[H: setTokenShape("Top down")]



<!-- get stats -->
[H: regex = "((\\d+) *[(][\\d +-]+[)]\\t*)"]
[h: id = strfind(statblock,regex)]

[h, if(getFindCount(id)), code: {
   [h: stats = json.append("[]","Strength","Dexterity","Constitution","Intelligence","Wisdom","Charisma")]
   [h, foreach(stat,stats), code: {
      [h: set(stat,getGroup(id,roll.count+1,2))]
   }]
}]

[H: '<!-- STATS END -->']

[H: '<!-- SET AC -->']
[H: id = strfind(statblock, "(?i)(?<!Hit )Armor Class.([0-9]*) (.*?)\Hit Points ")]
[H, IF(0< getFindCount(id)), CODE: {
   [AC=getGroup(id, 1, 1)]
      [ACC=getGroup(id, 1, 2)]
   [IF(AC==0):AC=10+Dx]

   [AC=""+AC+" "+ACC+""]
}]


[H: '<!-- SET Speed -->']

[H: id = strfind(statblock, "(?i)(?<!STR )(Speed.|burrow.|climb.|fly.|swim.)([0-9]+)")]
[H, IF(0< getFindCount(id)), CODE: {
   [Speed1=getGroup(id, 1, 2)]
   [IF(Speed1==""): setProperty("Speed1","")]
   [IF(Speed1!=""): Speed11=getGroup(id, 1, 2)]
   [IF(Speed11==""): setProperty("Speed11","")]

   
[Speed1="Move: "+Speed1+""]
[Spd1=""+Speed11+""]
} ;{
	[Speed1=""]
	[Spd1=0]

}]
[H, IF(1< getFindCount(id)), CODE: {
   [Speed2=getGroup(id, 2, 1)]
   [IF(Speed2==""): setProperty("Speed2","")]
   [IF(Speed2!=""): Speed22=getGroup(id, 2, 2)]
   [IF(Speed22==""): setProperty("Speed22","")]
[Speed2=""+upper(Speed2,1) + ": "+Speed22+""]
[Spd2=""+Speed22+""]
};{
	[Spd2=0]

}]
[H, IF(2< getFindCount(id)), CODE: {
   [Speed3=getGroup(id, 3, 1)]
   [IF(Speed3==""): setProperty("Speed3","")]
   [IF(Speed3!=""): Speed33=getGroup(id, 3, 2)]
   [IF(Speed33==""): setProperty("Speed33","")]
   [Speed3=""+upper(Speed3,1) + ": "+Speed33+""]
   [Spd3=""+Speed33+""]
};{
	[Spd3=0]

}]
[H, IF(3< getFindCount(id)), CODE: {
   [Speed4=getGroup(id, 4, 1)]
   [IF(Speed4==""): setProperty("Speed4","")]
   [IF(Speed4!=""): Speed44=getGroup(id, 4, 2)]
   [IF(Speed44==""): setProperty("Speed44","")]
   [Speed4=""+upper(Speed4,1) + ": "+Speed44+""]
   [Spd4=""+Speed44+""]
};{
	[Spd4=0]

}]
[H, IF(4< getFindCount(id)), CODE: {
   [Speed5=getGroup(id, 5, 1)]
   [IF(Speed5==""): setProperty("Speed5","")]
   [IF(Speed5!=""): Speed55=getGroup(id, 5, 2)]
   [IF(Speed55==""): setProperty("Speed55","")]
   [Speed5=""+upper(Speed5,1) + ": "+Speed55+""]
   [Spd5=""+Speed55+""]
};{
	[Spd5=0]

}]

[h:Speed=" "+Speed1+" "+Speed2+" "+Speed3+" "+Speed4+" "+Speed5+"" ]

[h: Spd= Max(eval("Spd1"),eval("Spd2"),eval("Spd3"),eval("Spd4"),eval("Spd5"))]



[H: '<!-- SET Hit Points -->']
[H: id = strfind(statblock, "Hit Points (\\d+) [(]([^)]+)[)]")]
[H, IF(0< getFindCount(id)), CODE: {
   [HP=getGroup(id, 1, 1)]
   [MaxHP=getGroup(id, 1, 1)]
   [HitDice=getGroup(id,1,2)]
}]

[H: '<!-- Hit Points END -->']



[H: '<!-- SET SIZE -->']
[H: id = strfind(statblock, "(Tiny|Small|Medium|Large|Huge|Gargantuan|\w-]+)")]
[H, IF(0< getFindCount(id)),CODE: {

   [Size=getGroup(id, 1, 1)]

   [setSize(Size)]

}]

[H: '<!-- SET TYPE -->']
[H: id = strfind(statblock,  "(aberration.*?|beast.*?|celestial.*?|construct.*?|dragon.*?|elemental.*?|fey.*?|fiend.*?|giant.*?|humanoid.*?|monstrosity.*?|ooze.*?|plant.*?|undead.*?|-)\,")]

[H, IF(0< getFindCount(id)),CODE: {
   [Type=getGroup(id, 1, 1)]
   [Type=upper(Type, 1)]

 }]

[H: '<!-- SET ALIGNMENT -->']
[H: id = strfind(statblock,  "\,.(lawful.|neutral.|chaotic.|)(good|neutral|unaligned|evil|\w-]+)")]

[H, IF(0< getFindCount(id)),CODE: {
   [Alignment1=getGroup(id, 1, 1)]
   [Alignment2=getGroup(id, 1, 2)]
   [Alignment1=upper(Alignment1,1)]
   [Alignment2=upper(Alignment2,1)]
   [Alignment1=trim(Alignment1,1)]
   [Alignment2=trim(Alignment2,1)]

   [IF(Alignment1==""): Alignment1=""]

[Alignment=""+Alignment1+" " + Alignment2+""]
 }]




[H: id = strfind(statblock, "passive Perception.([0-9]+)")]
[H, IF(0< getFindCount(id)), CODE: {
   [pass=getGroup(id, 1, 1)]
	
   [IF(pass>=0): setProperty("Passive Perception",pass)]
}]




[H: '<!-- Challenge BEGIN -->']
[H: id = strfind(statblock, "Challenge.([0-9]+)")]
[H, IF(0< getFindCount(id)), CODE: {
   [CR=getGroup(id, 1, 1)]
   [CR=trim(CR)]
   [IF(CR<=4): setProperty("SkillDie",2)]
   [IF(CR<=8&&CR>=5): setProperty("SkillDie",3)]
   [IF(CR<=12&&CR>=9): setProperty("SkillDie",4)]
   [IF(CR<=16&&CR>=13): setProperty("SkillDie",5)]
   [IF(CR<=20&&CR>=17): setProperty("SkillDie",6)]
   [IF(CR<=24&&CR>=21): setProperty("SkillDie",7)]
   [IF(CR<=28&&CR>=25): setProperty("SkillDie",8)]
   [IF(CR<=32&&CR>=29): setProperty("SkillDie",9)]

}]

[H: id = strfind(statblock, "Languages (.*) \Challenge")]
[H, IF(0< getFindCount(id)), CODE: {
   [Lang1=getGroup(id, 1, 1)]
   [Lang1 =trim(Lang1)]
   [Lang1 =upper(Lang1,1)]
   [IF(Lang1==""): setProperty("Lang1","")]




[Languages=" "+Lang1+""]
}]




[H: id = strfind(statblock, "(?i)\Damage Resistances?(.*?)(Damage Immunities|Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
   [Resist1=getGroup(id, 1, 1)]
   [Resist1=trim(Resist1)]
   [Resist1=upper(Resist1,1)]
   [IF(Resist1==""): setProperty("Resistance","")]
   [IF(Resist1!=""): setProperty("Resistance",Resist1)]
}]
[H: id = strfind(statblock, "(?i)\Damage Immunities?(.*?)(Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
   [Resist2=getGroup(id, 1, 1)]
   [Resist2=trim(Resist2)]
   [Resist2=upper(Resist2,1)]
   [IF(Resist2==""): setProperty("Immunities","")]
   [IF(Resist2!=""): setProperty("Immunities",Resist2)]
}]

[H: id = strfind(statblock, "(?i)\Condition Immunities?(.*?)(Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
   [Resist3=getGroup(id, 1, 1)]
   [Resist3=trim(Resist3)]
   [Resist3=upper(Resist3,1)]
   [IF(Resist3==""): setProperty("Condition Immunities","")]
   [IF(Resist3!=""): setProperty("Condition Immunities",Resist3)]
}]

[H: '<!-- SET SENSES AND SIGHT -->']
[h, IF(0==0), CODE:{
[H: id = strfind(statblock, "(blindsight.|darkvision.|tremorsense.|truesight.)([0-9]+)(.ft.).*?\,")]
[H, IF(0< getFindCount(id)), CODE: {
   [Sense1=getGroup(id, 1, 1)]
   [Sense2=getGroup(id, 1, 2)]
   [Sense3=getGroup(id, 1, 3)]
   [Sense1=trim(Sense1)]
   [Sense2=trim(Sense2)]
   [Sense3=trim(Sense3)]

[Sense1=upper(Sense1,1)]
   [IF(Sense1==""): Sense1=""]
   [IF(Sense2==""): Sense2=""]


[Senses=""+Sense1+" " + Sense2+" " + Sense3+" "]
[Sight1=(Sense1 +Sense2)]
[setSightType(Sight1)]
}]

[H, IF(2== getFindCount(id)), CODE: {
   [Sense4=getGroup(id, 2, 1)]
   [Sense5=getGroup(id, 2, 2)]
   [Sense6=getGroup(id, 2, 3)]
   [Sense4=trim(Sense4)]
   [Sense5=trim(Sense5)]
   [Sense6=trim(Sense6)]


[Sense4=upper(Sense4,1)]

   [IF(Sense4==""): Sense4=""]
   [IF(Sense5==""): Sense5=""]
[Senses=" "+Sense1+" " + Sense2+" " + Sense3+" " +Sense4+" " +Sense5+" " +Sense6+""]
[Sight2=(Sense4 +Sense5)]
 
[IF(Sense2>Sense5): setSightType(Sight1) ; setSightType(Sight2)]
}]

   [IF(Senses==""): setProperty("Senses","")]


   [IF(Senses==""): setHasSight(0)]
   [IF(Senses!=""): setHasSight(1)]
}]

[H: '<!-- SET XP TO GM NAME -->']
[H: id = strfind(statblock, "Challenge.[0-9]+.(.*XP.)")]
[H, IF(0< getFindCount(id)), CODE: {
   [XP=getGroup(id, 1, 1)]
	[setGMName(XP)]
  
}]
[H: '<!-- HATE BARS -->']
[h: setBarVisible("Health", 0)]


[h: ids=getTokenNames()]
[h:gTok=getTokenImage()] 
[h:iTok=getTokenHandout()] 
[h:setProperty("PIC1",gTok)]
[h:setProperty("PIC2",iTok)]

Triasmus
Kobold
Posts: 2
Joined: Thu Apr 16, 2020 11:34 am

Re: D&D 5e Statblock Importer.

Post by Triasmus »

I've made some updates to the importer.

I figured that if I'm going to use an importer, then it should also pull in the abilities and actions of the creatures, instead of just the stats.

With my updates, the importer will now also make macros for each ability and action. Each macro will have its description in the tooltip, and then clicking on the macro will also post the description to chat.

I don't know how well it works with pdfs, since I'm using dndbeyond.


ChangeLog:
  • I added a section near the top that will remove all macros from the token!! Be warned. It's named REMOVE ALL MACROS ON TOKEN
  • I removed the double space to single space replacement. Copying from dndbeyond turns newlines into spaces. I use those double spaces to help separate different actions and abilities.
  • Added Sections near bottom: ABILITIES, ACTIONS ALL, and ACTIONS TO-HIT/DMG MACROS
  • ABILITIES section: convert abilities to macros (The place before Actions on the statblock. Spellcasting, Legendary Resistances, etc.)
  • ACTIONS ALL section: gets actions and Legendary actions
  • ACTIONS TO-HIT/DMG MACROS section: Modifies some action macros created in the ACTIONS ALL section to use bobifle's library viewtopic.php?f=8&t=27420
    If you don't want to use bobifle's library, delete the ACTIONS TO-HIT/DMG MACROS section The attacks will still show what they do without that section, they would just no longer roll for you.
Bugs:
  • See the edit below. These aren't as much of a problem anymore
  • Abilites/actions that are described over multiple paragraphs will have an extra macro for each extra paragraph. Examples are the Aboleth Enslave ability and the Gold and Bronze Dragon's Change Shape action. There are not very many creatures that will meet this bug, and it's more of a minor hindrance to perfection than anything serious.
  • I've only used it with a comparatively few statblocks. If the name of an ability has more than letters and the characters: ' ()/ then there will likely be some issues with fully separating the abilities into macros.


Code
Spoiler

Code: Select all

[H: ids = getSelected()]
[H: abort(if(ids == "", 0, 1))]

[H: status=input("junk|Statblock info from Creature Name through Treasure line of Ecology (if it exists). Don't include flavor text/background/descriptions.||LABEL|SPAN=TRUE","junk|If Ecology section comes before Special Abilities, be sure to include Special Abilities section as well. Still no flavor text.||LABEL|SPAN=TRUE","statblock|Insert 5E statblock here|Enter statblock|TEXT|WIDTH=40")]
[H: abort(if(status < 1, 0, 1))]

[H: CRLF = decode("%0D%0A")]

[H: setPropertyType("5E-NPC")]
[H: GP=getProperty("dnd.weapons")]
[H: propnames = getPropertyNames()]
[H, foreach(propname,propnames),CODE: {
	[resetProperty(propname)]
}]

[H: setProperty("dnd.weapons", GP)]

[H: '<!-- REMOVE ALL MACROS ON TOKEN -->']
[H,foreach(lbl, getMacros()), code: {
	[indexes = getMacroIndexes(lbl)]
	[foreach(idx, indexes): removeMacro(idx)]
}]

[H: '<!-- Lets clean up those pesky non-ascii characters! -->']
[H: statblock = replace(statblock, "\\xD7", "x")]
[H: statblock = replace(statblock, "\\u2013", "-")]
[H: statblock = replace(statblock, "\\u2014", "-")]
[H: statblock = replace(statblock, "%E2%80%93", "-")]
[H: statblock = replace(statblock, "\\u2212", "-")]


[H: '<!-- change brackets and braces to parens to avoid stat sheet variable input error -->']
[H: statblock = replace(statblock, "[\\[\\{]","(")]
[H: statblock = replace(statblock, "[\\]\\}]",")")]


[H: setTokenShape("Top down")]
[h: setBarVisible("Health", 0)][H: '<!-- HATE BARS -->']

[h:gTok=getTokenImage()] 
[h:iTok=getTokenHandout()] 
[h:setProperty("PIC1",gTok)]
[h:setProperty("PIC2",iTok)]



[H: '<!-- Search for lines and sections of statblock individually -->']


[H: '<!-- SET NAME -->']
[H: NameSearch = strfind(statblock, "(.*?)(CoS|OotA|SKT|PotA|ToD|VGM|MM|Tiny|Small|Medium|Large|Huge|Gargantuan)")]
[H: THEName= getGroup(NameSearch, 1, 1)]
[H: NameSearch=trim(THEName)]
[H: setName(trim(THEName))]
[H: '<!-- END NAME -->']


[H: '<!-- SET SIZE -->']
[H: id = strfind(statblock, "(Tiny|Small|Medium|Large|Huge|Gargantuan|\w-]+)")]
[H, IF(0< getFindCount(id)),CODE: {
	[Size=getGroup(id, 1, 1)]
	[setSize(Size)]
}]
[H: '<!-- END SIZE -->']


[H: '<!-- SET TYPE -->']
[H: id = strfind(statblock,  "(aberration.*?|beast.*?|celestial.*?|construct.*?|dragon.*?|elemental.*?|fey.*?|fiend.*?|giant.*?|humanoid.*?|monstrosity.*?|ooze.*?|plant.*?|undead.*?|-)\,")]

[H, IF(0< getFindCount(id)),CODE: {
	[Type=getGroup(id, 1, 1)]
	[Type=upper(Type, 1)]

 }]
[H: '<!-- END TYPE -->']


[H: '<!-- SET ALIGNMENT -->']
[H: id = strfind(statblock,  "\,.(lawful.|neutral.|chaotic.|)(good|neutral|unaligned|evil|\w-]+)")]
[H, IF(0< getFindCount(id)),CODE: {
	[Alignment1=getGroup(id, 1, 1)]
	[Alignment2=getGroup(id, 1, 2)]
	[Alignment1=upper(Alignment1,1)]
	[Alignment2=upper(Alignment2,1)]
	[Alignment1=trim(Alignment1,1)]
	[Alignment2=trim(Alignment2,1)]

	[Alignment=""+Alignment1+" " + Alignment2+""]
 }]
[H: '<!-- END ALIGNMENT -->']


[H: '<!-- SET AC -->']
[H: id = strfind(statblock, "(?i)(?<!Hit )Armor Class.([0-9]*) (.*?)\Hit Points ")]
[H, IF(0< getFindCount(id)), CODE: {
	[AC=getGroup(id, 1, 1)]
	[ACC=getGroup(id, 1, 2)]
	[IF(AC==0):AC=10+Dx]

	[AC=""+AC+" "+ACC+""]
}]
[H: '<!-- END AC -->']


[H: '<!-- SET Hit Points -->']
[H: id = strfind(statblock, "Hit Points (\\d+) [(]([^)]+)[)]")]
[H, IF(0< getFindCount(id)), CODE: {
	[HP=getGroup(id, 1, 1)]
	[MaxHP=getGroup(id, 1, 1)]
	[HitDice=getGroup(id,1,2)]
}]
[H: '<!-- END Hit Points -->']


[H: '<!-- SET Speed -->']
[H: id = strfind(statblock, "(?i)(?<!STR )(Speed.|burrow.|climb.|fly.|swim.)([0-9]+)")]
[H, IF(0< getFindCount(id)), CODE: {
	[Speed1=getGroup(id, 1, 2)]
	[IF(Speed1==""): setProperty("Speed1","")]
	[IF(Speed1!=""): Speed11=getGroup(id, 1, 2)]
	[IF(Speed11==""): setProperty("Speed11","")]
	[Speed1="Move: "+Speed1+""]
	[Spd1=""+Speed11+""]
} ;{
	[Speed1=""]
	[Spd1=0]
}]
[H, IF(1< getFindCount(id)), CODE: {
	[Speed2=getGroup(id, 2, 1)]
	[IF(Speed2==""): setProperty("Speed2","")]
	[IF(Speed2!=""): Speed22=getGroup(id, 2, 2)]
	[IF(Speed22==""): setProperty("Speed22","")]
	[Speed2=""+upper(Speed2,1) + ": "+Speed22+""]
	[Spd2=""+Speed22+""]
};{
	[Spd2=0]
}]
[H, IF(2< getFindCount(id)), CODE: {
	[Speed3=getGroup(id, 3, 1)]
	[IF(Speed3==""): setProperty("Speed3","")]
	[IF(Speed3!=""): Speed33=getGroup(id, 3, 2)]
	[IF(Speed33==""): setProperty("Speed33","")]
	[Speed3=""+upper(Speed3,1) + ": "+Speed33+""]
	[Spd3=""+Speed33+""]
};{
	[Spd3=0]
}]
[H, IF(3< getFindCount(id)), CODE: {
	[Speed4=getGroup(id, 4, 1)]
	[IF(Speed4==""): setProperty("Speed4","")]
	[IF(Speed4!=""): Speed44=getGroup(id, 4, 2)]
	[IF(Speed44==""): setProperty("Speed44","")]
	[Speed4=""+upper(Speed4,1) + ": "+Speed44+""]
	[Spd4=""+Speed44+""]
};{
	[Spd4=0]
}]
[H, IF(4< getFindCount(id)), CODE: {
	[Speed5=getGroup(id, 5, 1)]
	[IF(Speed5==""): setProperty("Speed5","")]
	[IF(Speed5!=""): Speed55=getGroup(id, 5, 2)]
	[IF(Speed55==""): setProperty("Speed55","")]
	[Speed5=""+upper(Speed5,1) + ": "+Speed55+""]
	[Spd5=""+Speed55+""]
};{
	[Spd5=0]
}]

[h:Speed=" "+Speed1+" "+Speed2+" "+Speed3+" "+Speed4+" "+Speed5+"" ]

[h: Spd= Max(eval("Spd1"),eval("Spd2"),eval("Spd3"),eval("Spd4"),eval("Spd5"))]
[H: '<!-- END Speed -->']


[H: '<!-- SET STATS -->']
[H: regex = "((\\d+) *[(][\\d +-]+[)]\\t*)"]
[h: id = strfind(statblock,regex)]

[h, if(getFindCount(id)), code: {
	[h: stats = json.append("[]","Strength","Dexterity","Constitution","Intelligence","Wisdom","Charisma")]
	[h, foreach(stat,stats), code: {
		[h: set(stat,getGroup(id,roll.count+1,2))]
	}]
}]
[H: '<!-- END STATS -->']


[H: '<!-- SET SAVES, SKILLS, VULNERABILITIES, RESISTANCES, AND IMMUNITIES -->']
[H: id = strfind(statblock, "(?i)Saving Throws\?(.*?)(Skills|Damage Vulnerabilities|Damage Resistances|Damage Immunities|Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
	[svs=getGroup(id, 1, 1)]
	[svs=trim(svs)]
	[IF(svs==""): setProperty("Saves","");setProperty("Saves",svs)]
}]
[H: id = strfind(statblock, "(?i)Skills\?(.*?)(Damage Vulnerabilities|Damage Resistances|Damage Immunities|Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
	[skls=getGroup(id, 1, 1)]
	[skls=trim(skls)]
	[IF(skls==""): setProperty("Skills","");setProperty("Skills",skls)]
}]
[H: id = strfind(statblock, "(?i)\Damage Vulnerabilities?(.*?)(Damage Resistances|Damage Immunities|Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
	[Vuln=getGroup(id, 1, 1)]
	[Vuln=trim(Vuln)]
	[IF(Vuln==""): setProperty("Vulnerabilities","");setProperty("Vulnerabilities",Vuln)]
}]
[H: id = strfind(statblock, "(?i)\Damage Resistances?(.*?)(Damage Immunities|Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
	[Resist1=getGroup(id, 1, 1)]
	[Resist1=trim(Resist1)]
	[IF(Resist1==""): setProperty("Resistances","");setProperty("Resistances",Resist1)]
}]
[H: id = strfind(statblock, "(?i)\Damage Immunities?(.*?)(Condition Immunities|Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
	[Resist2=getGroup(id, 1, 1)]
	[Resist2=trim(Resist2)]
	[IF(Resist2==""): setProperty("Immunities","");setProperty("Immunities",Resist2)]
}]

[H: id = strfind(statblock, "(?i)\Condition Immunities?(.*?)(Senses)")]
[H, IF(0< getFindCount(id)), CODE: {
	[Resist3=getGroup(id, 1, 1)]
	[Resist3=trim(Resist3)]
	[IF(Resist3==""): setProperty("Condition Immunities","");setProperty("Condition Immunities",Resist3)]
}]
[H: '<!-- END VULNERABILITIES, RESISTANCES, AND IMMUNITIES -->']


[H: '<!-- Passive Perception -->']
[H: id = strfind(statblock, "Passive Perception.([0-9]+)")]
[H, IF(0< getFindCount(id)), CODE: {
	[pass=getGroup(id, 1, 1)]
	[IF(pass>=0): setProperty("Passive Perception",pass)]
}]
[H: '<!-- END Passive Perception -->']


[H: '<!-- SET SENSES AND SIGHT -->']
[H: id = strfind(statblock, "(Blindsight|Darkvision|Tremorsense|Truesight) ([0-9]+)(.ft..*?)\,")]
[H: longestsight=0]
[H, IF(0< getFindCount(id)),for(groupnum, 1, getFindCount(id)+1),CODE:{
	[sighttype=getGroup(id, groupnum, 1)]
	[range=getGroup(id, groupnum, 2)]
	[feet=getGroup(id, groupnum, 3)]

	[Senses=Senses+sighttype+" "+range+feet+", "]
	[IF(range>longestsight),code:{
		[longestsight=range]
		[setSightType(sighttype+range)]
	}]
}]
[H,IF(Senses==""):setHasSight(0);setHasSight(1)]
[H:setProperty("Senses",Senses+if(pass>=0," Passive Perception {getProperty('Passive Perception')}",""))]
[H: '<!-- END SENSES AND SIGHT -->']


[H: '<!-- LANGUAGES -->']
[H: id = strfind(statblock, "Languages (.*) \Challenge")]
[H, IF(0< getFindCount(id)), CODE: {
	[Lang1=getGroup(id, 1, 1)]
	[Lang1 =trim(Lang1)]
	[Lang1 =upper(Lang1,1)]
	[IF(Lang1==""): setProperty("Lang1","")]

	[Languages=" "+Lang1+""]
}]
[H: '<!-- END LANGUAGES -->']


[H: '<!-- CHALLENGE RATING -->']
[H: id = strfind(statblock, "Challenge.([0-9]+)")]
[H, IF(0< getFindCount(id)), CODE: {
	[CR=getGroup(id, 1, 1)]
	[CR=trim(CR)]
	[IF(CR<=4): setProperty("SkillDie",2)]
	[IF(CR<=8&&CR>=5): setProperty("SkillDie",3)]
	[IF(CR<=12&&CR>=9): setProperty("SkillDie",4)]
	[IF(CR<=16&&CR>=13): setProperty("SkillDie",5)]
	[IF(CR<=20&&CR>=17): setProperty("SkillDie",6)]
	[IF(CR<=24&&CR>=21): setProperty("SkillDie",7)]
	[IF(CR<=28&&CR>=25): setProperty("SkillDie",8)]
	[IF(CR<=32&&CR>=29): setProperty("SkillDie",9)]
}]
[H: id = strfind(statblock, "Challenge.([0-9/]+)")]
[H, IF(0< getFindCount(id)), CODE: {
	[CR=getGroup(id, 1, 1)]
	[CR=trim(CR)]}]
[H: '<!-- END CHALLENGE RATING -->']


[H: '<!-- SET XP TO GM NAME -->']
[H: id = strfind(statblock, "Challenge.[0-9/]+.\\(([0-9,]+.XP)\\)")]
[H, IF(0< getFindCount(id)), CODE: {
	[XP=getGroup(id, 1, 1)]
	[setGMName(XP)]
}]
[H: '<!-- END XP TO GM NAME -->']


[H: normalProps = "fontSize=1.10em;minWidth=180px;color=#b0b0f0;includeLabel=1;"]

[H: '<!-- SPECIAL TRAITS -->']
[H: id = strfind(statblock, "XP\\)  (.*?)  Actions")]
[H, IF(0<getFindCount(id)),CODE:{
	[abilities =getGroup(id,1,1)+"  z."]
	[H: id = strfind(abilities, "(.+?)\\. (.*?)  (?=[^:*,]+?\\.)")]
	[H, IF(0< getFindCount(id)),for(groupnum, 1, getFindCount(id)+1),CODE:{
		[abName=getGroup(id,groupnum,1)]
		[abDesc=getGroup(id,groupnum,2)]
		[abDesc = replace(abDesc, ";",",")]
		[abDesc = replace(abDesc, "  ","<br>")]
		[props = normalProps+"group=Special Traits;sortBy="+groupnum+";tooltip="+abDesc+";"]
		[createMacro(abName, abDesc, props)]
	}]
}]
[H: '<!-- END SPECIAL TRAITS -->']


[H: blockWithLegendary = statblock + "  Legendary"][H: '<!-- Used in both REACTIONS and ACTIONS AND LEGENDARY -->']
[H: '<!-- REACTIONS -->']
[H: id = strfind(blockWithLegendary, "  Reactions (.+?)(?=  Legendary)")]
[H, IF(0<getFindCount(id)),CODE:{
	[reactions =getGroup(id,1,1)+"  z."]
	[H: id = strfind(reactions, "(.+?)\\. (.*?)  (?=[^:*,]+?\\.)")]
	[H, IF(0< getFindCount(id)),for(groupnum, 1, getFindCount(id)+1),CODE:{
		[reName=getGroup(id,groupnum,1)]
		[reDesc=getGroup(id,groupnum,2)]
		[reDesc = replace(reDesc, ";",",")]
		[reDesc = replace(reDesc, "  ","<br>")]
		[props = normalProps+"group=Reactions;sortBy="+groupnum+";tooltip="+reDesc+";"]
		[createMacro(reName, reDesc, props)]
	}]
}]
[H: '<!-- END REACTIONS -->']


[H: '<!-- ACTIONS AND LEGENDARY-->']
[H: id = strfind(blockWithLegendary, "  (?:Legendary )?Actions (.+?)(?=(  Reactions)|(  Legendary))")]

[H, if(0<getFindCount(id)),CODE:{
	[actions=getGroup(id,1,1)+"  "]
};{
	[actions=""]
}]
[H, IF(0< getFindCount(id)),for(groupnum, 1, getFindCount(id)+1),CODE:{
	[actGroup = if(groupnum==1,"Actions","Legendary Actions")]
	[actions = getGroup(id,groupnum,1)+"  z."]
	[actid = strfind(actions, "(.+?)\\. (.*?)  (?=[^:*,]+?\\.)")]
	[IF(0< getFindCount(actid)),for(actgroupnum, 1, getFindCount(actid)+1),CODE:{
		[actionName=getGroup(actid,actgroupnum,1)]
		[actionDesc=getGroup(actid,actgroupnum,2)]
		[actionDesc = replace(actionDesc, ";",",")]
		[actionDesc = replace(actionDesc, "  ","<br>")]
		[props = normalProps+"group="+actGroup+";sortBy="+groupnum+actgroupnum+";tooltip="+actionDesc+";"]
		[actionName = replace(actionName, ",","")]
		[createMacro(actionName, actionDesc, props)]
	}]
}]
[H: '<!-- END ACTIONS AND LEGENDARY-->']



[H: '<!-- USING BOBIFLEs 5e addon lib -->']
[H: '<!-- ACTIONS TO-HIT/DMG MACROS -->']
[H: id = strfind(statblock, "(\\w+?)\\. (([\\w ]*?) Weapon Attack: \\+(\\d+).*?Hit: (.*?)\\.)")]
[H: "<!-- It would be better if I could use getMacroGroup(), but that function requires library code -->"]
[H,IF(0< getFindCount(id)),for(groupnum, 1, getFindCount(id)+1),CODE:{
	[actionName=getGroup(id,groupnum,1)]
	[actionDesc=getGroup(id,groupnum,2)]
	[actionRange=getGroup(id,groupnum,3)]
	[actionToHit=getGroup(id,groupnum,4)]
	[actionDmgs=getGroup(id,groupnum,5)]

	[iddmg = strfind(actionDmgs, "\\((.*?)(?: \\+ (\\d+)(?: plus (.*?))?)?\\) ([\\w]+?) damage")]
	[IF(0<getFindCount(iddmg)):dmgDie1 = getGroup(iddmg,1,1);dmgDie1 = "1d2"]
	[IF(0<getFindCount(iddmg)):dmgMod1 = getGroup(iddmg,1,2);dmgMod1="0"]
	[IF(0<getFindCount(iddmg)),code:{[dmgDie1 = if(getGroup(iddmg,1,3)!="",dmgDie1+" + "+getGroup(iddmg,1,3),dmgDie1)]}]
	[IF(0<getFindCount(iddmg)):dmgType1 = getGroup(iddmg,1,4);dmgType1="0"]
	[IF(1<getFindCount(iddmg)):dmgDie2 = getGroup(iddmg,2,1);dmgDie2 = "0"]
	[IF(1<getFindCount(iddmg)):dmgMod2 = getGroup(iddmg,2,2);dmgMod2 = "0"]
	[IF(1<getFindCount(iddmg)),code:{[dmgDie2 = if(getGroup(iddmg,2,3)!="",dmgDie2+" + "+getGroup(iddmg,2,3),dmgDie2)]}]
	[IF(1<getFindCount(iddmg)):dmgType2 = getGroup(iddmg,2,4);dmgType2 = "0"]

	[H:indexes = getMacroIndexes(actionName)]
	[H,if(1==listCount(indexes)),CODE:{
		[actionDesc = getMacroCommand(listGet(indexes,0))]
		[actionDesc = replace(actionDesc,"'","")]
	}]

	[cmd = encode("[h:jsonWeaponData = json.set('{}',")]
	[cmd = cmd+encode("'Name', '"+actionName+"',")]
	[cmd = cmd+encode("'DamageDie', '"+dmgDie1+"',")]
	[cmd = cmd+encode("'DamageBonus','"+dmgMod1+"',")]
	[cmd = cmd+encode("'DamageType','"+dmgType1+"',")]
	[cmd = cmd+encode("'HitBonus','"+actionToHit+"',")]
	[cmd = cmd+encode("'SecDamageType','"+dmgType2+"',")]
	[cmd = cmd+encode("'SecDamageDie','"+dmgDie2+"',")]
	[cmd = cmd+encode("'SecDamageBonus','"+dmgMod2+"',")]
	[cmd = cmd+encode("'SpecialAbility','',")]
	[cmd = cmd+encode("'Description', '"+actionDesc+"',")]
	[cmd = cmd+encode("'FlavorText','"+NameSearch+" attacks with "+actionName+"!',")]
	[cmd = cmd+encode("'ButtonColor','green',")]
	[cmd = cmd+encode("'FontColor','white')]")]
	[cmd = cmd+encode("[macro('NPCAttack@Lib:Addon5e'):jsonWeaponData]")]
	[H,if(1==listCount(indexes)),CODE:{
		[setMacroCommand(listGet(indexes,0),decode(cmd))]
	};{
		[createMacro(actionName, decode(cmd), normalProps+"group=Combat;")]
	}]
}]
[H: '<!-- END ACTIONS TO-HIT/DMG MACROS -->']

[H: props = "fontSize=1.10em;group=Sheet;minWidth=85px;color=yellow;sortBy=z;"]
[H: createMacro("Sheet", "[macro('Sheet@Lib:Addon5e'):0]", props)]
[H: props = "fontSize=1.10em;group=Sheet;minWidth=85px;color=green;fontColor=white"]
[H: createMacro("CheckMe", "[macro('CheckMe@Lib:Addon5e'):0]", props)]
[H: createMacro("SaveMe", "[macro('SaveMe@Lib:Addon5e'):0]", props)]
[H: '<!-- END USING BOBIFLEs 5e addon lib -->']





EDIT:
I've updated the code in the block

Updates:
  • Renamed "Abilities" to "Special Traits" to conform to how they're referred to in the SRD.
  • I've organized the code. Now everything is gotten in the order that it appears in a statsheet (I figured it's easier to find what I'm looking for if it's organized like that).
  • I've added a section that get's reactions.
  • The regex that gets abilities is smarter. Now any paragraph with a starting sentence that does not have a colon or comma will be considered part of the previous paragraph (most paragraphs that have a comma are a continuation of an ability description; most paragraphs that have a colon are part of spellcasting(e.g. "Cantrips (at will):"). I have seen only three problems with this approach: Tiamat Legendary actions, her breath weapons have a colon; Beholder eye rays, The macro for the ray is just named a number; and Amble (a tortle from Lacathah Rising), She has one trait that bleeds into her next trait, since it has a single sentence paragraph at the end of it that doesn't have a colon or comma).
  • I did a minor update to the 5E-NPC properties
    Spoiler
    @Strength (Str):8
    @Dexterity (Dex):8
    @Constitution (Con):8
    @Intelligence (Int):8
    @Wisdom (Wis):8
    @Charisma (Cha):8
    Str:{floor((Strength - 10)/2)}
    Dex:{floor((Dexterity - 10)/2)}
    Con:{floor((Constitution - 10)/2)}
    Int:{floor((Intelligence - 10)/2)}
    Wis:{floor((Wisdom - 10)/2)}
    Cha:{floor((Charisma - 10)/2)}
    Modifier:[]
    RechargePowers:{}
    RequiresRecharge
    HitDice:1d6
    @SkillDie:0
    @InitMod:0
    CurrentHitDice:{CR}
    *Type:Grue
    *@#Alignment
    *@Class
    *@CR:{Level}
    #Level
    HP:0
    *@HPDisplay (HP):{HP+TempHP} ({MaxHP})
    MaxHP:0
    @TempHP:0
    *@AC:0
    *@Speed
    Description (Des)
    #DailySpells:[]
    #DailyPower1
    #DailyPower2
    #DailyPower3
    #DailyPower4
    #DailyPower5
    *@Vulnerabilities
    Resistances
    Resist:{getProperty("Resistances")}
    *@sheet.Resist (Resistances):[r, if(! json.isEmpty(Resist)): substring(Resist,0,min(length(Resist),35)); ""]
    *@sheet.specialR2 (Resist. Ext. -):[r, if(Resist == "NA" || Resist == "" || length(Resist) <= 35): ""; substring(Resist,35,min(length(Resist),70))]
    *@sheet.specialR3 (Resist. Ext. --):[r, if(Resist == "NA" || Resist == "" || length(Resist) <= 70): ""; substring(Resist,70,min(length(Resist),105))]
    *@sheet.specialR4 (Resist. Ext. ---):[r, if(Resist == "NA" || Resist == "" || length(Resist) <= 105): ""; substring(Resist,105,min(length(Resist),150))]
    *@Immunities
    Condition Immunities
    ConI:{getProperty("Condition Immunities")}
    *@sheet.ConI (Condition Immunities):[r, if(! json.isEmpty(ConI)): substring(ConI,0,min(length(ConI),35)); ""]
    *@sheet.specialC2 (Con. Imm. Ext. -):[r, if(ConI == "NA" || ConI == "" || length(ConI) <= 35): ""; substring(ConI,35,min(length(ConI),70))]
    *@sheet.specialC3 (Con. Imm. Ext. --):[r, if(ConI == "NA" || ConI == "" || length(ConI) <= 70): ""; substring(ConI,70,min(length(ConI),105))]
    *@sheet.specialC4 (Con. Imm. Ext. ---):[r, if(ConI == "NA" || ConI == "" || length(ConI) <= 105): ""; substring(ConI,105,min(length(ConI),150))]
    *@Languages
    Skills:[]
    @CritRange:20
    *@Passive Perception:{10+Wis}
    *@Note
    Charges
    Spd:30
    Speed1:""
    Speed2:""
    Speed3:""
    Speed4:""
    Speed5:""
    First:0
    Second:0
    Third:0
    Fourth:0
    Fifth:0
    Sixth:0
    Seventh:0
    Eighth:0
    Ninth:0
    XP
    Rage
    *@Luck
    *@#Stealth
    *@Ki
    *@#ActionSurge
    *@#WildShapes
    HealingPotion
    WSRetainedHP
    CurShape
    ChannelDivinity
    RageDmg
    Exhaustion:0
    SecondWind
    SpellSave:{8+SkillDie+ Max (Wis,Int,Cha)}
    SpellAttack:{SkillDie+ Max (Wis,Int,Cha)}
    *@SpellSlots (Spell Slots)
    Concentration
    *sheet.appearance (Appearance)
    *sheet.IsBloodied (Is Bloodied):[r:if(HP/MaxHP < 0.5, "YES!", "")]
    *@sheet.physical (Str Dex Con):[R: strformat("%s(%+d) %s(%+d) %s(%+d)",Strength,Str,Dexterity,Dex,Constitution,Con)]
    *@sheet.mental (Int Wis Cha):[R: strformat("%s(%+d) %s(%+d) %s(%+d)",Intelligence,Int,Wisdom,Wis,Charisma,Cha)]
    Coinpurse
    Weightload:{Strength*15}
    DeathFail:0
    DeathSucc:0
    dnd.weapons
    Inventory:[]
    *Conditions:[h:theList = getTokenStates()][r, foreach(item, theList, ""): if(getState(item), item + ", ", "")]
    *size
    Senses
    PIC1:[]
    PIC2:asset://cf90363732c7d24e997eefee527bbeb5
    InitAmmo:0
    Initiative:{Dex}
  • I had to add a Darkvision240 sight to my sights, for Tiamat
  • I created a library macro that will display the stat sheet for these imported monsters. I added the sheet macro to my version of bobifle's Lib, since It's a modified version of the sheet macro that is on most/all of the tokens that he shares with his Library. This macro must be on a library token, since it uses getMacroGroup()
    Spoiler

    Code: Select all

    [frame("Token_Sheet"): {
    <html>
     <head>
     [r: "<style>
     .sblock { background: #FDF1DC; }
     table.sblock {
       width: 100%;
       border: 0px;
       border-collapse: collapse;
     }
     th, td {
      width: 50px;
      text-align: center;
     }
     h1 { font-family: 'Libre Baskerville', 'Lora', 'Calisto MT',
          'Bookman Old Style', Bookman, 'Goudy Old Style',
          Garamond, 'Hoefler Text', 'Bitstream Charter',
          Georgia, serif;
          color: #7A200D;
          font-weight: 700;
          margin: 0px;
          font-size: 23px;
          letter-spacing: 1px;
          font-variant: small-caps;
        }
     h2 {
      font-weight: normal;
      font-style: italic;
      font-size: 12px;
      margin: 0;
     }
    
     .propline {
      line-height: 1.4;
      display: block;
      text-indent: -1em;
      padding-left: 1em;
      }
    
     h4 {
      color: #7A200D;
      margin: 0;
      display: inline;
      font-weight: bold;
     }
     p {
      text-indent: 1em;
      margin: 0;
     }
    	.rule { height: 2px; background: #922610; position: relative; }
       </style>"]
      <title>{getName()} Details</title>
     </head>
     <body>
     <div class="sblock">
        <creature-heading>
          <h1>{getName()}</h1>
          <h2>{getProperty("Type")}, {getProperty("Alignment")}</h2>
        </creature-heading>
    	<div class="rule"></div>
        <div class="property-line">
         <h4>Armor Class</h4>
         <p>{getProperty("ac")}</p>
        </div>
        <div class="propline">
         <h4>Hit Points</h4>
         <p>{getProperty("MaxHP")} ({getProperty("hitdice")})</p>
        </div>
        <div class="propline">
         <h4>Speed</h4>
         <p>{getProperty("speed")}</p>
        </div>
    	<div class="rule"></div>
        <table class="sblock">
        <tr> <th>STR</th><th>DEX</th><th>CON</th><th>INT</th><th>WIS</th><th>CHA</th> </tr>
        <tr>
          <td id="str">{getProperty("Strength")} ({getProperty("Str")})</td>
          <td id="dex">{getProperty("Dexterity")} ({getProperty("Dex")})</td>
          <td id="con">{getProperty("Constitution")} ({getProperty("Con")})</td>
          <td id="int">{getProperty("Intelligence")} ({getProperty("Int")})</td>
          <td id="wis">{getProperty("Wisdom")} ({getProperty("Wis")})</td>
          <td id="cha">{getProperty("Charisma")} ({getProperty("Cha")})</td>
        </tr>
        </table>
    	<div class="rule"></div>
    	 <p><b>Saves</b> {getProperty("Saves")}</p>
    	 <p><b>Skills</b> {getProperty("Skills")}</p>
    	 <p><b>Vulnerabilities</b> {getProperty("Vulnerabilities")}</p>
    	 <p><b>Resistances</b> {getProperty("Resistances")}</p>
    	 <p><b>Immunities</b> {getProperty("Immunities")} </p>
    	 <p><b>Condition Immunities</b> {getProperty("Condition Immunities")} </p>
    	 <p><b>Senses</b> {getProperty("Senses")}</p>
    	 <p><b>Languages</b> {getProperty("Languages")}</p>
    	 <p><b>Challenge</b> {getProperty("CR")} ({getGMName()})</p>
    	<div class="rule"></div>
    
    	[foreach(idx, getMacroGroup("Special Traits")), code: {
    		[h:props = getMacroProps(idx,"json")]
    		[h:label =json.get(props,"label")]
    		[h:tooltip = json.get(props,"tooltip")]
    		<p><b>[r:label].</b> [r:tooltip]</p>
    	}]
    	
    	<p></p>
         <h3>ACTIONS</h3>
    	<div class="rule"></div>
    	
    	[foreach(idx, getMacroGroup("Actions")), code: {
    		[h:props = getMacroProps(idx,"json")]
    		[h:label =json.get(props,"label")]
    		[h:tooltip = json.get(props,"tooltip")]
    		<p><b>[r:label].</b> [r:tooltip]</p>
    	}]
    
    	[H:test = 0]
    	[h,foreach(idx, getMacroGroup("Reactions")), code: {
    		[H:test = test+1]
    	}]	
    	[if(test>0),code:{
    		<p></p>
    	     <h3>REACTIONS</h3>
    		<div class="rule"></div>
    	}]
    		
    	[foreach(idx, getMacroGroup("Reactions")), code: {
    		[h:props = getMacroProps(idx,"json")]
    		[h:label =json.get(props,"label")]
    		[h:tooltip = json.get(props,"tooltip")]
    		<p><b>[r:label].</b> [r:tooltip]</p>
    	}]
    
    	[H:test = 0]
    	[h,foreach(idx, getMacroGroup("Legendary Actions")), code: {
    		[H:test = test+1]
    	}]	
    	[if(test>0),code:{
    		<p></p>
    	     <h3>LEGENDARY ACTIONS</h3>
    		<div class="rule"></div>
    	}]
    		
    	[foreach(idx, getMacroGroup("Legendary Actions")), code: {
    		[h:props = getMacroProps(idx,"json")]
    		[h:label =json.get(props,"label")]
    		[h:tooltip = json.get(props,"tooltip")]
    		<p><b>[r:label].</b> [r:tooltip]</p>
    	}]
    	
     </div>
     </body>
    </html>
    }]
  • I decided to import each statblock with macros that call some functions in bobifle's 5eAddon Library
  • There were some bugs in bobifle's Library that I had to fix. I'll probably upload all of this as a framework once I'm satisfied with it (I still need to at least search through for actions, like dragon's breath, that do damage with a Save instead of an attack, then modify those macros to do the damage rolls and say what save to make).
Last edited by Triasmus on Fri Apr 17, 2020 4:41 pm, edited 1 time in total.

Triasmus
Kobold
Posts: 2
Joined: Thu Apr 16, 2020 11:34 am

Re: D&D 5e Statblock Importer.

Post by Triasmus »

I haven't commented enough to edit my previous post.

I have confirmed that the general macros do not work with pdfs. Weapon Attack actions will still get a macro generated that can can use the bobifle library to roll for you, though any rider effects won't be shown in those macros (ex. the Aboleth's Tentacle. It has the attack plus a rider effect that requires a save. That rider won't be shown in the macro at all.)

pixel7777
Kobold
Posts: 9
Joined: Wed Nov 06, 2019 1:34 pm

Re: D&D 5e Statblock Importer.

Post by pixel7777 »

XKnives wrote:
Fri Apr 10, 2020 5:19 am
I believe I mended the issue causing 'Stats in statblock not found.' assert to go off.
It was a minor change, I removed the 2nd regex matching.
Thanks so much - this worked for me!

Merudo
Giant
Posts: 228
Joined: Wed Jun 05, 2019 7:06 am

Re: D&D 5e Statblock Importer.

Post by Merudo »

The source code for 5etools has the data for every creature in JSON form. That could probably be imported into MapTool without too much problem.

Post Reply

Return to “Macros”