[AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Discussion concerning lmarkus' campaign framework for D&D3.x and Pathfinder.

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

Forum rules
Discussion regarding lmarkus001's framework only. Other posts deleted without notice! :)
User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

[AM Macros] Lindsay's PF/3.5 Campaign FW (11-13-15)

Post by aliasmask »

I've been using the MT1.3b87.02 Campaign Framework for awhile. I've made so many changes that I just never upgraded to the next one. Also, if I find a small bug I may just fix it and not report it. Whenever I come up with a new innovation for the framework I'll post the changes made here. Sometimes it's a modification of the Lib:libDnD35Pathfinder token and others may be an update to my lib:players or Lib:spells3 tokens or other aspects of the framework. Most of my changes will focus on Pathfinder, but I'll try not to break any 3.5 stuff.

This space will hold the collection of these changes.

My latest campaign file:
New Name
PF B89 Core2 5-27-15

Features:
  • Auto-check for flanking. Only checks immediately around target. Plans to expand code.
  • Attack table - Includes attack label, AC and Damage.
    • Label has tooltip for attack and damage mods.
    • manyshot doesn't display AC (because if 1st attack hits, then manyshot hits)
    • Damage has link to apply damage to selected token. OneClick prevents it from being applied more than once to same token. Total crit damage is in red.
    • Attacker and Targets have clickable images. One click selects token or moves view to token if already selected.
    • miss chance (##%), fumble and crit (##) in red are displayed next to AC when appropriate. Tool tip shows Alternate roll if no crit threat is made and 2 miss chances are listed if player doesn't select in attack.
  • Impersonate Macro - Popup frame for in character chat with special formatting options. In charcter chat log can be exported by running saveChatTranscripts on lib:players
  • Auto-Flurry - When flurry is checked, rapid shot and full round are selected.
  • Auto-Point Blank - When target 1 is selected, range test is performed if point blank is not already selected and check if within 30ft.
  • bullRushSpellPF - spell based bull rush used for hydraulic push and torrent. Macros on Wey token show possible uses.
  • Call Tokens/Move Tokens - Output cleaned up. Show To and From in chat and moves view to those maps and positions (not just tokens). If you don't select token and click link, then is just shows goto link for view change. Good for using on player hidden maps.
  • am.adjustHP(type,amount,tokenIds) - can be used in chat or anywhere that evaluates code. tokenIds is optional and defaults to selected tokens otherwise. Only tokens you own can be updated. Output uses mini portrait.
Change Log:
  • Update 3-27-14
    • Added Elevation button
    • Buttons and Groups re-organized for Campaign Window (optimized for 2 columns)
    • Updated am.adjustHP to display in output when target is unconscious (also in tooltip)
    • Added Higher Ground auto-check
  • Lots o updates 1-17-15
  • 1-22-15 - fixed crit during autohit
  • 1-25-15 - fixed black screening in player view on NPCs
  • 2-04-15 - updated initiative to not show players npc initiatives
  • 2-04-15 - added pfStatBlockImport with a couple of minor adjustments.
  • 3-15-15 - update to pfStatBlockImport to fix statsheet variable input error when [] or {} is used in displayed field.
  • 5-4-15 - updated Full HP and stateChangeAliasmask to save and change back the token shape when altered by setLayer (MT bug).
  • Updated by Ray to include more spells 12-20-14
  • Fixed level up for clerics. Removed spell convert question if already set. 12-20-14
Video Tutorials: TBA

Updates:
  • lib:spells3 - Fixed bad Class Independent special abils "Generate CI Straight Links" when a blank item is created by using a comma in the name. 8-9-15
  • lib:spells3 - Many more fixes and enhancements.
  • lib:spells3 - 9-1-15 latest set of fixes.
  • (10-29-15) Lib:libDnD35Pathfinder | pfStatBlockImporter
  • (10-29-15) Lib:libDnD35Pathfinder | onCampaignLoad,getCharXpGold,getMonsterXp,initXpTables,tabTable
  • (11-13-15) Added macro for Mirror Image. Includes images for state and "Counter" bar.

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

[reserved] - Cross version compatible macros

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

Custom macros used with my custom tokens

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

changeElevation(): output

This will change the elevation of the current token, set states and output change to chat.
\\\ changeElevation ///

Code: Select all

@@ @changeElevation
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy= ; color=white ; playerEditable=true ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- changeElevation(): output -->
[H: elevation.value = getProperty("Elevation")]
[H: elevation.states = getProperty("elevation.states")]
[H, if(! isNumber(elevation.value)): elevation.value = 0]
[H, if(! isNumber(elevation.states)): elevation.states = 0]

[H: abort(input("tip|<html><b><i>Either set current elevation or put adjustment value in.</i></b></html>||LABEL|SPAN=TRUE",
   strformat('elevation.input|%{elevation.value}|Current Elevation|TEXT'),
   "elevation.adjustment|0|Adjust Elevation|TEXT",
   "elevation.metric|1|Apply Upward Move Cost|CHECK",
   strformat('elevation.states|%{elevation.states}|Set Elevation States|CHECK'),
   strformat('elevation.fly|%{state.Fly}|Set Fly State|CHECK')
))]

<!-- error checking -->
[H, if(! isNumber(elevation.input)): elevation.input = 0]
[H, if(! isNumber(elevation.adjustment)): elevation.adjustment = 0]

<!-- get new elevation -->
[H, if(elevation.input != elevation.value), code: {
   [H: elevation.new = elevation.input]
};{
   [H, if(elevation.adjustment > 0), code: {
      [H, if(elevation.metric): elevation.new = elevation.value + floor(elevation.adjustment/10) * 5; elevation.new = elevation.value + elevation.adjustment]
   };{
      [H: elevation.new = max(0,elevation.value - elevation.adjustment)]
   }]
}]

<!-- enforce 5ft increments -->
[H: elevation.new = floor(elevation.new/5)*5]

<!-- clear elevation states -->
[H, for(i,5,155,5): setState(i,0)]

<!-- set states -->
[H: state.Fly = elevation.fly]
[H, if(elevation.new > 0 && elevation.states): setState(min(150,elevation.new),1)]

<!-- set elevation value -->
[H: setProperty("Elevation",elevation.new)]
[H: setProperty("elevation.states",elevation.states)]

<!-- output elevation changes -->
[H, if(state.Fly): moveText = "FLIES"; moveText = "MOVES"]
[H: tokenImage = am.tokenImageLink(currentToken())]

[H: output = strformat('
   <table style="border-spacing:0px;border-style:solid;border-color:black;border-width:1pt;padding:0px">
      <tr>
         <td width="34" style="padding:0px">%{tokenImage}</td>
         <td width="250" style="background-color:aqua;padding:0px 5px 2px 5px;">%{token.name} %{moveText} to Elevation %{elevation.new}.</td>
      </tr>
   </table>
')]

[H: am.play.output(output)]

!!
 
Requires: onCampaignLoad definition, am.play.output() (or change output code), am.tokenImageLink() (or alter/remove call)
  • onCampaignLoad Definition

    Code: Select all

    [H: defineFunction("am.changeElevation","changeElevation@"+getMacroLocation(),1)]
    am.play.output() - is a part of lib:players, but can change to this:

    Code: Select all

    [H: broadcast(output)]
    am.tokenImageLink() - custom macro to show token image and stats. May be clicked to select token or move to token. An alternate solution is to use this:

    Code: Select all

    [H: tokenImage = strformat('<img src="%s" height=30 width=30 />',getImage(currentToken()))]
    
Usage:

Code: Select all

[H: am.changeElevation()]
This can be made a campaign or token macro. Preferences are saved on to token.
Attachments
Change Elevation Popup.jpg
Change Elevation Popup.jpg (26.89 KiB) Viewed 13902 times
Change Elevation Output.jpg
Change Elevation Output.jpg (36.73 KiB) Viewed 13902 times

Elorebaen
Dragon
Posts: 365
Joined: Sat Dec 22, 2007 5:37 pm

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by Elorebaen »

Thank you for sharing alias!

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

I made some changes, but it's not very "drop-in" oriented. I added a bunch of state images and changed the code to handle more type of movement like swim and burrow. I used another contributors images from this site and tweaked them slightly and added my own numbering. There's no easy way to import the images other than doing it one at a time. I'll post some images to help you install it for yourself.

You'll need to add the states as corner image, upper right under the Elevations group name.
||| CODE |||

Code: Select all

@@ @changeElevation
<!-- changeElevation(): output -->
[H: elevation.start = getProperty("Elevation")]
[H: elevation.states = getProperty("elevation.states")]
[H: elevation.type = getProperty("elevation.type")]
[H, if(! isNumber(elevation.start)): elevation.value = 0; elevation.value = elevation.start]
[H, if(! isNumber(elevation.states)): elevation.states = 0]

[H: elevationTypes = ",Fly,Levitate,Climb,Burrow,Swim,Above,Below"]
[H: type.index = max(0,listFind(elevationTypes,elevation.type))]

[H: abort(input("tip|<html><b><i>Either set current elevation or put adjustment value in.</i></b></html>||LABEL|SPAN=TRUE",
   strformat('elevation.input|%{elevation.value}|Current Elevation|TEXT'),
   "tip|<html><b>* <i>Fly, Levitate, Climb and Above are positive while the others appear as negative.</i></b></html>||LABEL|SPAN=TRUE",
   "elevation.adjustment|0|Adjust Elevation|TEXT",
   "tip|<html><b>* <i>Elevation Adjustment for Fly is halved going up and doubled going down.</i></b></html>||LABEL|SPAN=TRUE",
   strformat('elevation.type|%{elevationTypes}|Set Elevation Type|LIST|VALUE=STRING SELECT=%{type.index}')
))]

<!-- error checking -->
[H, if(! isNumber(elevation.input)): elevation.input = 0]
[H: elevation.value = elevation.input]
[H, if(! isNumber(elevation.adjustment)): elevation.adjustment = 0]

[H, if(elevation.type == ""): elevation.states = 0; elevation.states = 1]

<!-- get new elevation -->
[H, if( elevation.type == "Fly"), code: {
   [H, if(elevation.adjustment > 0): elevation.adjustment = floor(elevation.adjustment/10) * 5; elevation.adjustment = elevation.adjustment * 2]
};{}]


[H: elevation.new = elevation.value + elevation.adjustment]
[H, if(! elevation.states && elevation.new == 0): elevation.type = ""]

[H: limit = 0]
[H, if(listContains("Burrow,Below",elevation.type)): limit = -5]
[H, if(elevation.type == "Above"): limit = 5]

<!-- enforce 5ft increments -->
[H: elevation.new = floor(elevation.new/5)*5]
[H, if(listContains("Burrow,Swim,Below",elevation.type)): elevation.new = min(limit,elevation.new); elevation.new = max(limit,elevation.new)]

<!-- clear elevation states -->
[H, for(i,5,155,5): setState(i,0)]
[H: newStates = getTokenStates("json","Elevations")]
[H, foreach(state,newStates): setState(state,0)]

<!-- set states -->
[H, if(elevation.states), code: {
   [H: setState("Elevation"+elevation.type,1)]
   <!-- convert elevation to state name -->
   [H: elevation.string = max(-95,min(995,elevation.new))]
   [H, if(elevation.string < 0): setState("negative",1)]
   [H: elevation.string = abs(elevation.string)]
   [H: elevation.100 = floor(elevation.string / 100)]
   [H: elevation.10 = floor((elevation.string - elevation.100 * 100)/10)]
   [H: elevation.1 = elevation.string - (elevation.100 * 100) - (elevation.10 * 10)]
   [H, if(elevation.100), code: {
      [H: setState("e"+elevation.100+"__",1)]
      [H: setState("e_"+elevation.10+"_",1)]
      [H: setState("e__"+elevation.1,1)]
   };{
      [H, if(elevation.10): setState("e_"+elevation.10+"_",1)]
      [H: setState("e__"+elevation.1,1)]
   }]   
};{}]

[H, if(! elevation.states && elevation.new == 0): elevation.type = ""]

<!-- set elevation value -->
[H: setProperty("Elevation",elevation.new)]
[H: setProperty("elevation.states",elevation.states)]
[H: setProperty("elevation.type",elevation.type)]

<!-- output elevation changes -->
[H: tokenImage = am.tokenImageLink(currentToken())]

[H: output = strformat('
   <table style="border-spacing:0px;border-style:solid;border-color:black;border-width:1pt;padding:0px">
      <tr>
         <td width="34" style="padding:0px">%{tokenImage}</td>
         <td width="250" style="background-color:aqua;padding:0px 5px 2px 5px;">%{token.name} (<b>%{elevation.type}</b>): Elevation changed from %{elevation.start} to %{elevation.new}.</td>
      </tr>
   </table>
')]

[H: am.play.output(output)]

!!
 
change elevation example.jpg
change elevation example.jpg (78.67 KiB) Viewed 13738 times
||| IMAGES |||
state images.jpg
state images.jpg (48.79 KiB) Viewed 13738 times
Elevation States.jpg
Elevation States.jpg (119.99 KiB) Viewed 13738 times
I haven't tested this, but you can probably omit the images as long as you don't choose the state type, like fly or swim. It'll just adjust the elevation value, but if you're going to do that, you may as well use the original code above.

Edit: Here is a link to my latest campaign with these changes: PF B89 Core2 5-31-14
Attachments
Elevation Images.zip
(241.05 KiB) Downloaded 166 times

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

I made an update to getRangedDistance to speed it up a bit when elevations are the same, which is true most of the time.

Code: Select all

@@ @getRangedDistance
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy= ; color=white ; playerEditable=true ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- getRangedDistance(source,target): range -->
[H: source = arg(0)]
[H: target = arg(1)]

<!-- source and target must be defined -->
[H: assert(! json.isEmpty(source) && ! json.isEmpty(target),"<b>getDnDRange(source,target): range</b> - Source or target not defined.",0)]

<!-- If elevation isnt set then set to 0 -->
[H: source.elevation = getProperty("Elevation",source)]
[H, if(! isNumber(source.elevation)): source.elvation = 0]
[H: target.elevation = getProperty("Elevation",target)]
[H, if(! isNumber(target.elevation)): target.elvation = 0]

[H, if(source.elevation == target.elevation), code: {
   [H: totalDistance = getDistance(source,1,target,"ONE_TWO_ONE")]
};{
   <!-- get elevation difference -->
   [H: distance.vertical = abs(source.elevation - target.elevation)]
   <!-- count squares, no metric in between -->
   [H: distance.direct = getDistance(source,1,target,"ONE_ONE_ONE")]
   <!-- get number of diagonal moves -->
   [H: horizontal.diagonals = floor((getDistance(source,1,target,"NO_DIAGONALS") - distance.direct)/5)]
   <!-- its assumed when changing elevation that you move in a diagonal fashion until at elevation -->
   [H: vertical.diagonals = min(distance.direct,distance.vertical)]
   <!-- subtract horizontal diags used from vertical diags needed because we can move diag in vert and horizontal direction for same cost -->
   [H: extra.diagonals =  floor((max(vertical.diagonals,horizontal.diagonals) - horizontal.diagonals)/5)]
   <!-- manually calc distance based on number of diags moved (1-2-1) and add the leftover horizonal and vertical distances -->
   [H: totalDistance = floor((horizontal.diagonals + extra.diagonals) * 0.5) * 5 + distance.direct + distance.vertical - vertical.diagonals]
}]

[H: macro.return = totalDistance]

!!

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

New Update.

I included the difference in size. Bigger creatures reduce the distance to creatures at higher elevations.
||| getRangedDistance |||

Code: Select all

@@ @getRangedDistance
<!-- getRangedDistance(source,target): range -->
[H: source = arg(0)]
[H: target = arg(1)]

<!-- source and target must be defined -->
[H: assert(! json.isEmpty(source) && ! json.isEmpty(target),"<b>getRangedDistance(source,target): range</b> - Source or target not defined.",0)]

<!-- If elevation isnt set then set to 0 -->
[H: source.elevation = getProperty("Elevation",source)]
[H, if(! isNumber(source.elevation)): source.elevation = 0]
[H: target.elevation = getProperty("Elevation",target)]
[H, if(! isNumber(target.elevation)): target.elevation = 0]

<!-- get elevation difference and factor in size. Used big formula for processing speed -->
[H, if(source.elevation != target.elevation), code: {
   [H, if(source.elevation < target.elevation): distance.vertical = max(0,target.elevation - (source.elevation + (listFind("Large,Huge,Gargantuan,NA,Colossal",getSize(source)) + 1)*5));
      distance.vertical = max(0,source.elevation - (target.elevation + (listFind("Large,Huge,Gargantuan,NA,Colossal",getSize(target)) + 1)*5))]
};{
   [H: distance.vertical = 0]
}]

[H, if(! distance.vertical), code: {
   [H: totalDistance = getDistance(source,1,target,"ONE_TWO_ONE")]
};{
   <!-- count squares, no metric in between -->
   [H: distance.direct = getDistance(source,1,target,"ONE_ONE_ONE")]
   <!-- get number of diagonal moves -->
   [H: horizontal.diagonals = floor((getDistance(source,1,target,"NO_DIAGONALS") - distance.direct)/5)]
   <!-- its assumed when changing elevation that you move in a diagonal fashion until at elevation -->
   [H: vertical.diagonals = min(distance.direct,distance.vertical)]
   <!-- subtract horizontal diags used from vertical diags needed because we can move diag in vert and horizontal direction for same cost -->
   [H: extra.diagonals =  floor((max(vertical.diagonals,horizontal.diagonals) - horizontal.diagonals)/5)]
   <!-- manually calc distance based on number of diags moved (1-2-1) and add the leftover horizonal and vertical distances -->
   [H: totalDistance = floor((horizontal.diagonals + extra.diagonals) * 0.5) * 5 + distance.direct + distance.vertical - vertical.diagonals]
}]

[H: macro.return = totalDistance]

!!
 
I also updated the stat sheet range calculation to call the above. It's actually a little bit faster with the updates.
||| distanceStatSheet |||

Code: Select all

@@ @distanceStatSheet
<!-- distanceStatSheet(sourceToken): distanceList
   sourceToken - source token to measure selected tokens to. Current token being moused-over by design.
   distanceList - A list of selected token to measure distance to target token.
   
This function will return a string of selected tokens and their distance to target token.
   created 3-21-13 by AM
   modified 3-25-13 by W42
      reversed target/source logic and showed full list of selected targets
   modified 3-20-14 by AM
      added calculation for Elevation. REQUIRES the Elevation token property to default to 0 under token type
      formula = distance + max(0,elevationDifference - numDiagonals)
   modified 11-25-14 by AM
      uses call to getRangedDistance which calculates for creature size in how it affects elevation distance
      no longer shows distance to self when multiple tokens selected
-->
[H: source = arg(0)]
[H: selected = json.difference(getSelected("json"),json.append("",source))]
[H: output = ""]
[H, foreach(token,selected), code: {
   [H: totalDistance = am.getRangedDistance(source,token)]
   [H: output = listAppend(output,strformat("%s (%{totalDistance})",getName(token)))]
}]

[H: macro.return = output]

!!
 

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

This tweak updates the subModEdit macro which is what is used for creating/editing Active Mods and Items. I've tested this on the latest release 1.3.b91.03 of Lindsay's 3.5/PF FW. and as far back as the 1.3b87.02 of his framework with no problems.

It doesn't change much of the content, but does put mod fields in to a tab format making it easier to find what you want to change.
edit mods example.jpg
edit mods example.jpg (61.15 KiB) Viewed 13365 times
||| subModEdit |||

Code: Select all

@@ @subModEdit
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=1.00em ; sortBy=20 ; color=red ; playerEditable=false ; applyToSelected=true ; group=Utility Mods ; tooltip= ; minWidth= ; 
[H: " modType 0 = buff, 1 = item " ]

[H: modType = json.get( macro.args, "modType" ) ]

[H: tToken = currentToken() ]

[H: modStatCheckFieldList = table( "SysVars", json.get( table( "SysVars", 0 ), "modStatCheckFieldList" ) ) ]
[H: modList = table( "SysVars", json.get( table( "SysVars", 0 ), "modFieldList" ) ) ]
[H: modArmorFieldList = table( "SysVars", json.get( table( "SysVars", 0 ), "modArmorFieldList" ) ) ]
[H: modSpeedFieldList = table( "SysVars", json.get( table( "SysVars", 0 ), "modSpeedFieldList" ) ) ]
[H: modDRERFieldList = table( "SysVars", json.get( table( "SysVars", 0 ), "modDRERFieldList" ) ) ]

[H: tFullList = table( "SysVars", json.get( table( "SysVars", 0 ), "modTypeList" ) ) ]
[H: modArmorList = table( "SysVars", json.get( table( "SysVars", 0 ), "modArmorList" ) ) ]
[H: tNASList = list.difference( tFullList, modArmorList ) ]

[H: statesList = getTokenStates() ]

[H, MACRO( "subBaselineArmor@this" ): json.set( "{}", "tokenID", tToken ) ]

[H, SWITCH( modType ), CODE:
   case 0: {
      [ gTok = "Lib:GlobalsSRDPF" ]
      [ jTM = getLibProperty( "TempModToggleSets", gTok ) ]
      [ jActive = json.get( PrivateJSON, "ActiveTempModSets" ) ]
   }; 
   case 1: {
      [ jTM = BonusTypedItems ]
      [ jActive = json.get( PrivateJSON, "ActiveItems" ) ]
   }; 
   default: {
      [ assert( 0, "Missing modType calling parameter.", 1 ) ]
   }
]

[H: output = ""]
[H: tjMods = "{}" ]
[H: tglobalMod = 0 ]

[H: tjMods = json.set( tjMods, "globalMod", json.set( "{}", "v", 0, "t", 0 ) ) ]
[H, FOREACH( m, modStatCheckFieldList ): tjMods = json.set( tjMods, m, json.set( "{}", "v", 0, "t", 0 ) ) ]
[H, FOREACH( m, modList ): tjMods = json.set( tjMods, m, json.set( "{}", "v", 0, "t", 0 ) ) ]
[H, FOREACH( m, modArmorFieldList ): tjMods = json.set( tjMods, m, json.set( "{}", "v", 0, "t", 0 ) ) ]
[H, FOREACH( m, modSpeedFieldList ): tjMods = json.set( tjMods, m, json.set( "{}", "v", 0, "t", 0 ) ) ]
[H, FOREACH( m, modDRERFieldList ): tjMods = json.set( tjMods, m, json.set( "{}", "v", 0, "t", 0 ) ) ]

[H, SWITCH( modType ), CODE:
   case 0: {
      [ jTMnew = json.set( "{}", "duration", 0, "cat", "Misc", "state", "NA", "macro", "", "mods", tjMods, "note", "", "tip", "" ) ]
   }; 
   case 1: {
      [ jTMnew = json.set( "{}", "mods", tjMods, "cat", "Misc",  "state", "NA", "macro", "", "acp", 0, "maxdex", 50, "encumbered", 0, "w", 0, "note", "", "tip", "" ) ]
   }; 
   default: {
   }
]

[H, IF( json.type( jTM ) == "UNKNOWN" || json.isEmpty( jTM ) ): jTM = "{}" ]

[H: tmList = "" ]
[H: tCatList = "[]" ]
[H, FOREACH( i, jTM ), CODE: {
   [ jtmpM = json.get( jTM, i ) ]
   [IF( json.contains( jtmpM, "cat" ) ), CODE: {
      [ tCategory = json.get( jtmpM, "cat" ) ]
      [ tCatList = json.append( tCatList, tCategory ) ]
   }; {
      [ tCategory = "Misc" ]
   }]
   [ tmList = listAppend( tmList, "[ " + tCategory + " ] ~~ " + i ) ]
}]
[H: tCatList = json.toList( json.sort( json.unique( tCatList ), "a" ) ) ]
[H: tCatList = listAppend( "(Other)", tCatList ) ]
[H: tmList = listSort( tmList, "A+" ) ]

[H: actionList = "Edit,Add New,Delete,Give Item to ..." ]
[H, SWITCH( modType ), CODE:
   case 0: {
      [ actionList = listDelete( actionList, 3 ) ]
      [ actionList = if( isGM(), actionList, listDelete( actionList, 2 ) ) ]
   }; 
   case 1: {
   }; 
   default: {
   }
]

[H: cancel = 1 ]
[H, IF( tmList == "" ): setAction = 1; cancel = input(
  "setAction|" + actionList + "|Choose Action|RADIO|ORIENT=H",
  "tmID|" + tmList + "|Which?|RADIO|VALUE=STRING"
)]
[H: abort( cancel ) ]

<!-- Toggle off the mod if active -->

[H, IF( tmList != "" ): tmID = listGet( tmID, 1, " ~~ " ) ]

<!-- " ADD NEW " -->
[H, IF( setAction == 1 ), CODE: {
  [ jTM = json.set( jTM, "New", jTMnew ) ]
  [ tmID = "New" ]
  [ setAction = 0 ]
}]

[H: jTMID = json.get( jTM, tmID ) ]
[H: tjMods = json.merge( tjMods, json.get( jTMID, "mods" ) ) ]

[H: state = json.get( jTMID, "state" ) ]
[H: tmacro = json.get( jTMID, "macro" ) ]
[H: tnote = json.get( jTMID, "note" ) ]
[H, IF( json.contains( jTMID, "cat" ) ): tCategory = json.get( jTMID, "cat" ); tCategory = "Misc" ]
[H, IF( modType == 1 ): tacp = json.get( jTMID, "acp" ) ]
[H, IF( modType == 1 ): tmaxdex = json.get( jTMID, "maxdex" ) ]
[H, IF( modType == 1 ): tencumbered = json.get( jTMID, "encumbered" ) ]

[SWITCH( setAction ), CODE:
   case 0: {  <!-- " MODIFY " -->
      [H: tFields = json.fields( tjMods ) ]
      [H: inModString0 = 'tip|--------------------------------||LABEL|SPAN=TRUE##tip|Enter a formula or variable name:  max(1,floor((BardLevel-2)/6)+1) .||LABEL|SPAN=TRUE##v%{m}|%{v}|%{m}|TEXT|WIDTH=30##tip|<html>Enter the value: <b>globalMod</b> in subsequent fields to use this calculated variable.</html>||LABEL|SPAN=TRUE</html>||LABEL|SPAN=TRUE' ]
      [H: inModString1 = 'v%{m}|%{v}|<html><body%{bgColor}><b>%{m}</b></body></html>|TEXT|WIDTH=30' ]
      [H: inModString2 = 't%{m}|%{tNAS}|Type|LABEL|' ]
      [H: inModString3 = 't%{m}|%{tFullList}|Type|LIST|SELECT=%{t}' ]
      [H: inModString4 = 't%{m}|%{tNASList}|Type|LIST|SELECT=%{t}' ]
   
      [H: inListMod = "" ]
      
      [H: newListTrigger = "StrengthCheckMod,StrMod,FortBonus,SkillMisc,MiscATK,Armor,Base,DR_All"]
      [H: newListTabName = "NOT USED,Stat Checks,Stat Mods,Saves,Skills,Combat,Armor,Movement,DR ER SR"]
      [H: changeFlag = -1]
      
      [H, FOREACH( m, tFields ), CODE: {
         [ newListIndex = listFind(newListTrigger,m) + 1]
         [ if(newListIndex): inListMod = replace(inListMod,"_bgcolor_",if(changeFlag,"yellow","white"))]
         [ if(newListIndex): tabName = listGet(newListTabName,newListIndex)]
         [ if(newListIndex): inListMod = listAppend(inListMod,strformat("tabvar|<html><div bgcolor=_bgcolor_>%{tabName}</div></html>|%{tabName}|TAB ##tip|<html><b>Enter a signed Integer ( - for penalty ), formula, variable name or <i>globalMod</i>.<br>   * Do not leave any fields blank. Use 0 for no entry.</b><br></html>||LABEL|SPAN=TRUE"),"##")]
         [ if(newListIndex): changeFlag = 0]

         [ isNASDD = if( listContains( modArmorList, m, "," ), 1, 0 )]
         [ isArmorField = if( listContains( modArmorFieldList, m ) > 0, 1, 0 ) ]
         [ isDRER = if( listContains( modDRERFieldList, m ) > 0, 1, 0 ) ]
         
         [ tjM = json.get( tjMods, m ) ]
         [ v = json.get( tjM, "v" ) ]
         [ t = json.get( tjM, "t" ) ]
         [IF( isArmorField && !isNASDD ): t = listFind( tNASList, listGet( tFullList, t ) ) ]
         [IF( isArmorField && isNASDD ): t = listFind( tFullList, m ) ]
         [IF( isDRER ): t = listFind( tFullList, "Resistance" ) ]
         [IF( v != 0 ): bgColor = " bgColor=#CCFF22"; bgColor = "" ]
         [ if(v != 0 && changeFlag != -1): changeFlag = 1]

         [ inListMod = listAppend( inListMod, strformat( if( m == "globalMod", inModString0, inModString1 ) ), "##" ) ]

         [ tNAS = listGet( tFullList, t ) ]
         [IF( m != "globalMod" ):  inListMod = listAppend( inListMod, strformat( if( isNASDD || isDRER, inModString2, if( isArmorField, inModString4, inModString3 ) ) ), "##" ) ]
      }]
      
      [H: inListMod = replace(inListMod,"_bgcolor_",if(changeFlag,"yellow","white"))]
      
      [H: tmID = if( tmID == "New", "Required but Use No Commas Semicolons or Quotes", tmID ) ]

      [H, IF( modType == 0 ): inType = if( isGM(), "tmacro|" + tmacro + "|Custom Macro to Run|TEXT|WIDTH=35", "" ) ]
      [H, IF( modType == 1 ): inType = "tacp|" + tacp + "|Armor Check Penalty|TEXT|WIDTH=2##tmaxdex|" + tmaxdex + "|Max Dexterity Bonus|TEXT|WIDTH=2##tencumbered|" + tencumbered + "|Encumbering?|CHECK|" ) ]

      [H: cancel = input( "tabvar|<html><div bgcolor=white>Core</div></html>|Core|TAB",
         "tmID|" + tmID + "|Enter Identifying Name|TEXT|WIDTH=35",
         "tmCat|" + tCatList + "|Display Category|LIST|VALUE=STRING SELECT=" + listFind( tCatList, tCategory ),		 
         "state|NA," + statesList + "|Associate a State Image?|LIST|VALUE=STRING SELECT=" + listFind( "NA," + statesList, state ),
         + inType + "",
         "tnote|" + tnote + "|Notes|TEXT|WIDTH=50",
         + inListMod + ""
      )]
      [H: abort( cancel ) ]
  
      [H: tmID = replace( replace( replace( replace( replace( tmID, ",", "_" ), "; ", "_" ), "'", "_" ), "`", "_" ), " ~~ ", " ~ " ) ]
      [H: assert( !( tmID == "" || tmID == "New" ), "Aborting Edit: Required Identifier missing.", 0 ) ]
      [H: tmacro = if( tmacro == 0, "", tmacro )]
      [H: tnote = if( tnote == 0, "", tnote )]
      [H, IF( modType == 1 ): tacp = 0 - abs( tacp ) ]
	  [H, IF( tmCat == "(Other)" ), CODE: {
	     [H: cancel = input(
	        "tmCat|Misc|Enter New Display Category|TEXT|WIDTH=35"
         )]
		 [H: abort( cancel ) ]
	  }; {}]
  
      [H: tjMods = "{}" ]
      [H, FOREACH( m, tFields ), CODE: {
         [ tv =  eval( "v" + m ) ]
         [ tm = 0 ]
         [ isNASDD = if( listContains( modArmorList, m, "," ), 1, 0 )]
         [ isArmorField = if( listContains( modArmorFieldList, m ) > 0, 1, 0 ) ]
         [ isDRER = if( listContains( modDRERFieldList, m ) > 0, 1, 0 ) ]
         [IF( tv != 0 && isNASDD ): tm = listFind( tFullList, m ) ]
         [IF( tv != 0 && !isNASDD && isArmorField ): tm = listFind( tFullList, listGet( tNASList, eval( "t" + m ) ) ) ]
         [IF( tv != 0 && isDRER ): tm = listFind( tFullList, "Resistance" ) ]
         [IF( tv != 0 && !isNASDD && !isArmorField && !isDRER ): tm = eval( "t" + m ) ]
         [IF( tv != 0 ): tjMod = json.set( "{}", "v", tv, "t", tm ) ]
         [IF( tv != 0 ): tjMods = json.set( tjMods, m, tjMod ) ]
      }]

      [H, IF( modType == 1 ): tTipLead = "<table border='0' cellspacing='0' cellpadding='0'>" + if( state == "NA", "", "<tr><td align='left' valign='top'><b>State Icon</b>: </td><td align='left' colspan='2'>  " + state + "</td></tr>" ) + if( tmaxdex >= 50, "", "<tr><td align='left' valign='top'><b>Max Dex Bonus</b>: </td><td>  " + tmaxdex + "</td><td></td></tr>" ) + if( tacp == 0, "", "<tr><td align='left' valign='top'><b>Armor Check Penalty</b>: </td><td>  " + tacp + "</td><td></td></tr>" ) + if( tencumbered == 0, "", "<tr><td align='left' valign='top'><b>Encumbering</b>: </td><td>X</td><td></td></tr>" ) ]
      [H, IF( modType == 0 ): tTipLead = "<table border='0' cellspacing='0' cellpadding='0'>" + if( state == "NA", "", "<tr><td align='left' valign='top'><b>State Icon</b>: </td><td align='left' colspan='2'>  " + state + "</td></tr>" ) ]
      
      [H: sfTip = "<tr><td align='left' valign='top'><b>%{m}</b>: </td><td align='right'>%{tipv}</td><td align='left'><i>   %{t}</i></td></tr>" ]
      [H: sfGMTip = "<tr><td align='left' valign='top'><b>%{m}</b>: </td><td colspan='2'>%{tipv}</td></tr>" ]

      [H: bTip = "" ]
      [H, FOREACH( m, json.fields( tjMods ) ), CODE: {
         [ tipv = json.get( json.get( tjMods, m ), "v" ) ]
         [IF( m == "globalMod" ): t = ""; t = listGet( tFullList, json.get( json.get( tjMods, m ), "t" ) ) ]
         [IF( tipv != 0 ): bTip = concat( bTip, strformat( if( m == "globalMod", sfGMTip, sfTip ) ) ) ]
      }]

      [H, IF( tnote == "" ): nTip = ""; nTip = "<table border='0' cellspacing='0' cellpadding='0' width='400px'><tr><td>" + tnote + "</td></tr></table>" ]
      [H: tTip = concat( tTipLead, bTip, "</table>", nTip ) ]
      [H: tTip = decode( replace( encode(tTip), "%22", "%60" ) ) ]
      [H, IF( modType == 1 ): jTM = json.set( jTM, tmID, json.set( "{}", "mods", tjMods, "cat", tmCat, "state", state, "macro", tmacro, "acp", tacp, "maxdex", tmaxdex, "encumbered", tencumbered, "w", 0, "note", tnote, "tip", tTip ) ) ]
      [H, IF( modType == 0 ): jTM = json.set( jTM, tmID, json.set( "{}", "duration", 0, "cat", tmCat, "state", state, "macro", tmacro, "mods", tjMods, "note", tnote, "tip", tTip ) ) ]
      
      [H: jTM = json.remove( jTM, "New" ) ]

      [H, IF( modType == 1 ): setProperty( "BonusTypedItems", jTM ) ]
      [H, IF( modType == 0 ): setLibProperty( "TempModToggleSets", jTM, gTok ) ]
  
      [H: output = concat( '<span title="<html>', tTip, '</html>"><b>Edited</b>: <i>', tmID, '</i></span>' ) ]
   }; 
   case 1: {
      
   }; 
   case 2: {  <!-- " DELETE " -->
      [H: tTip = json.get( json.get( jTM, tmID ), "tip" ) ]
      [IF( json.contains( jActive, tmID ) > 0 ), CODE: {
         [MACRO("subModToggle@this"): json.set( "{}", "tokenID", tToken, "setID", tmID, "setType", modType ) ]
      }; {}]
      [H: jTM = json.remove( jTM, tmID ) ]
      [H, IF( modType == 1 ): setProperty( "BonusTypedItems", jTM ) ]
      [H, IF( modType == 0 ): setLibProperty( "TempModToggleSets", jTM, gTok ) ]

      [H: output = '<span title="<html>' + tTip + '</html>"><b>Deleted</b>: <i>' + tmID + '</i>.</span>' ]
   }; 
   case 3: {  <!-- " GIVE " -->
      [MACRO("giveItem@this"): json.set( "{}", "tokenID", tToken, "setID", tmID, "setType", modType ) ]
   }; 
   default: {
   }
]

[IF(output != ""), CODE:
  {
     [R, S, G: output]
  }; {}
]

[H, IF( isFrameVisible( token.name ) ), CODE:
{
   [MACRO( "CharSheet@this" ): "Page=Main ; Token=" + tToken ]
}; {}]

[H: link = macroLinkText("subModEdit@lib:libDnD35Pathfinder","gm-self",json.set( "{}", "modType", modType ),currentToken())]
[H: execLink(link,1)]

!!
The core of the change is in this code:

Code: Select all

      [H: newListTrigger = "StrengthCheckMod,StrMod,FortBonus,SkillMisc,MiscATK,Armor,Base,DR_All"]
      [H: newListTabName = "NOT USED,Stat Checks,Stat Mods,Saves,Skills,Combat,Armor,Movement,DR ER SR"]
      [H: changeFlag = -1]
      
      [H, FOREACH( m, tFields ), CODE: {
         [ newListIndex = listFind(newListTrigger,m) + 1]
         [ if(newListIndex): inListMod = replace(inListMod,"_bgcolor_",if(changeFlag,"yellow","white"))]
         [ if(newListIndex): tabName = listGet(newListTabName,newListIndex)]
         [ if(newListIndex): inListMod = listAppend(inListMod,strformat("tabvar|<html><div bgcolor=_bgcolor_>%{tabName}</div></html>|%{tabName}|TAB ##tip|<html><b>Enter a signed Integer ( - for penalty ), formula, variable name or <i>globalMod</i>.<br>   * Do not leave any fields blank. Use 0 for no entry.</b><br></html>||LABEL|SPAN=TRUE"),"##")]
         [ if(newListIndex): changeFlag = 0]

         [ isNASDD = if( listContains( modArmorList, m, "," ), 1, 0 )]
         [ isArmorField = if( listContains( modArmorFieldList, m ) > 0, 1, 0 ) ]
         [ isDRER = if( listContains( modDRERFieldList, m ) > 0, 1, 0 ) ]
         
         [ tjM = json.get( tjMods, m ) ]
         [ v = json.get( tjM, "v" ) ]
         [ t = json.get( tjM, "t" ) ]
         [IF( isArmorField && !isNASDD ): t = listFind( tNASList, listGet( tFullList, t ) ) ]
         [IF( isArmorField && isNASDD ): t = listFind( tFullList, m ) ]
         [IF( isDRER ): t = listFind( tFullList, "Resistance" ) ]
         [IF( v != 0 ): bgColor = " bgColor=#CCFF22"; bgColor = "" ]
         [ if(v != 0 && changeFlag != -1): changeFlag = 1]

         [ inListMod = listAppend( inListMod, strformat( if( m == "globalMod", inModString0, inModString1 ) ), "##" ) ]

         [ tNAS = listGet( tFullList, t ) ]
         [IF( m != "globalMod" ):  inListMod = listAppend( inListMod, strformat( if( isNASDD || isDRER, inModString2, if( isArmorField, inModString4, inModString3 ) ) ), "##" ) ]
      }]
      
      [H: inListMod = replace(inListMod,"_bgcolor_",if(changeFlag,"yellow","white"))]
It basically looks for certain mod names and then creates a new tab for that data. Any tabs that have values in them will be highlighted.

I actually plan to do another update to this macro sometime in the future. One would be to toggle off any macro that is being edited. This will prevent those random mod bonuses when you don't unequip an item being edited. I also plan to add another field that will list the client name, time, location and round it was activated. I was also thinking of adding an optional duration field which can be a formula. It would just be for show, but may have more functionality in the future. I would also like to add individual skill mods.

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

One more update. I've added a "Mod Stamp" to when mods and items are activated. The stamp can be viewed in the Mod Report. This one touches a couple of macros so here we go.

The first line is the player who set the mod and the date/time based on their system activated. The second line is the map and x,y coordinate it was activated. I figure this would be useful to know where it was done so the GM can get an idea of time passed based on where the players where on a map. The third line is the combat round, if any. -1 means out of combat. This is most useful for short duration spells and effects like haste.
Mod Stamp Example.jpg
Mod Stamp Example.jpg (32.29 KiB) Viewed 13356 times
onCampaignLoad - add these 2 lines. Run onCampaignLoad after adding to the macros below.

Code: Select all

[H: defineFunction("am.setModStamp","setModStamp@"+thisLib,1)]
[H: defineFunction("am.getModStamp","getModStamp@"+thisLib,1)]
subModToggle - put the one line near the end and before the output like below:

Code: Select all

[H, IF( listContains( statesList, state ) > 0 ): setState( state, !active ) ]

[H: am.setModStamp(tmID,active,currentToken())]

[R,S,G, IF( tmacro != ""), MACRO( tmacro ): json.set( "{}", "token", tToken, "active", active, "casterlevel", tclvl ) ]
ConditionReport - place the stamp get after modTip and update the output line with stamp data. This is in 2 locations in the code, but looks the same:

Code: Select all

   [H: modTip = replace(modTip,'"',""")]
   [H: stamp = am.getModStamp(mod,currentToken())]
   
   [H: '<!-- create output -->']
   [H: modLineOutput = strformat('<span title="<html><table><tr valign=top><td>%{stateImage}</td><td>%{modTip}%{stamp}</td></tr></table></html>">%{mod}</span>')]
And now add the 2 new macros:

Code: Select all

@@ @setModStamp
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy= ; color=white ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- setModStamp(modName,active,tokenId) 
   modName - name of mod to relate stamp too
   active - current state of stamp. If active then delete, if off then add stamp
   tokenId - id of token
   
This function will save activation info on current token to be viewed in mod report
-->
[H: modName = arg(0)]
[H: active = arg(1)]
[H: tokenId = arg(2)]

[H: modStamp = getProperty("am.modStamp",tokenId)]
[H, if(json.isEmpty(modStamp)): modStamp = "{}"]
[H, if(active), code: {
   [H: modStamp = json.remove(modStamp,modName)]
};{
   [H: stamp = strformat("<table><tr><td><b>%s:</b> %s<br><b>Location:</b> %s (%s)<br><b>Round:</b> %s</td></tr></table>",
      getPlayerName(), json.get(getInfo("client"),"timeDate"), getCurrentMapName(),getTokenX(0,tokenId)+","+getTokenY(0,tokenId), getInitiativeRound())]
   [H: modStamp = json.set(modStamp,modName,stamp)]
}]
[H: setProperty("am.modStamp",modStamp,tokenId)]

!!
@@ @getModStamp
@PROPS@ fontColor=black ; autoExecute=true ; fontSize=11pt ; sortBy= ; color=white ; playerEditable=false ; applyToSelected=false ; group=AM Updates ; tooltip= ; minWidth=94 ; 
<!-- getModStamp(modName,tokenId) -->
[H: modName = arg(0)]
[H: tokenId = arg(1)]

[H: modStamp = getProperty("am.modStamp",tokenId)]
[H, if(json.isEmpty(modStamp)): modStamp = "{}"]

[H: macro.return = json.get(modStamp,modName)]

!!

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

I update to PF Statblock Importer
  • First I changed it from a macro to a UDF. I changed the campaign macro name to TOKEN IMPORT.
    Bug?
    The players were able to see the import data which I think is a bug in b91. I just realized it probably has something to do with mixing html comments with output restricted to g,s. The g,s output is still being sent to everyone not just gm and self. I first noticed this with initiative macro. There may be something going on, but that's my initial assessment of that bug.
  • On some imports all the statblock data was being dumped in to the name, fixed that.
  • When other characters appear after hp and before hd it wouldn't process hp or any other data after that. Mostly because some statblocks would look like hp 13 each (2d8+4).
  • NPCs starting with sight on is a pain because it blacks out the screen when you try to move them with certain server settings. So, I changed the default to off.
  • Valid Vulnerability Check. I ran in to an instance where it listed a vulnerability that wasn't standard and it really messed up the token by adding it to the list on token. So, it only adds the standard ones.
Campaign Macros wrote:
\\\ TOKEN IMPORT ///

Code: Select all

[H, if(isGM()): am.pfStatBlockImport()]
Lib:libDnD35Pathfinder wrote:
\\\ onCampaignLoad ///

Code: Select all

...
[H: defineFunction("am.pfStatBlockImport","pfStatBlockImport@"+thisLib,1)]
...
\\\ PFStatBlockImport ///

Code: Select all

<!-- Statblock2Token v2.0.3 (2-25-15)
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
LGM: Corrected vulnerability, Added Feats
AM (v2.0.3): fixed hp each; name get; default sight off; valid vulnerability check; made UDF; output change 
-->
[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 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)]
}]

<!-- 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, "Ecolo gy", "Ecology")]
[H: statblock = replace(statblock, "  ", " ")]

<!-- Start formatting imported statblock and set it to GM notes -->

<!-- If Ecology section comes before Special Abilities, cut it out and put it last. If the Ecology section doesn't exist, create a blank one last -->
[H: EcologyThenSAbSearch = strfind(statblock, "(?i)(ECOLOGY)(.*?)(Special Abilities)")]
[H, IF(0<getFindCount(EcologythenSAbSearch)), CODE: {
	[IF(getGroup(EcologyThenSAbSearch, 1, 3) == "special abilities" || getGroup(EcologyThenSAbSearch, 1, 3) == "Special Abilities" || getGroup(EcologyThenSAbSearch, 1, 3) == "Special abilities" || getGroup(EcologyThenSAbSearch, 1, 3) == "SPECIAL ABILITIES"), CODE: {
		[EcologyString = getGroup(EcologyThenSAbSearch, 1, 0)]
		[statblock = replace(statblock, "(?i)((ECOLOGY)(.*?))((Special Abilities)(.*))", "\$4  \$1" )]
		[EcologySearch = strfind(statblock, "(?i)(ECOLOGY)(.*)")]
	};{
	}]
};{
	[EcologySearch = strfind(statblock, "(?i)(ECOLOGY)(.*)")]
	[IF(0<getFindCount(EcologySearch)), CODE: {
		[statblock = statblock]
	};{
		[statblock = statblock + " ECOLOGY "]
		[EcologySearch = strfind(statblock, "(?i)(ECOLOGY)(.*)")]
	}]
}]

<!-- Search for lines and sections of statblock individually -->
[H: NameSearch = strfind(statblock, "(.*?)CR")]
[H: CRSearch = strfind(statblock, "(CR ?[0-9]+(/[0-9]+)?)")]
[H: XPSearch = strfind(statblock, "(XP [0-9,]+)")]
[H: GenderEtcSearch = strfind(statblock, "(?i)XP [0-9,]+ ([a-z ,0-9]*)( LG | LN | LE | NG | N | NE | CG | CN | CE )")]
[H: AlignmentSizeRaceSearch = strfind(statblock, "(?i)(( LG | LN | LE | NG | N | NE | CG | CN | CE ).*?)Init")]
[H: InitSensesSearch = strfind(statblock, "(?i)(Init.*?[0-9]) +(Aura|DEFENSE|Weakness)")]
[H: AuraSearch = strfind(statblock, "(?i)(Aura.*?) DEFENSE")]
[H: ACStringSearch = strfind(statblock, "(?i)(AC [0-9].*?\\)) +HP")]
[H: HPStringSearch = strfind(statblock, "(?i)((HP) .*?)Fort ")]
[H: SavesSearch = strfind(statblock, "(?i)(Fort (.*?))(Defensive Abilit|DR [0-9]|Immune|Resist|SR|Weakness|OFFENSE)")]
[H: DefensiveAbilitiesSearch = strfind(statblock, "(?i)((Defensive Abilities|Defensive Ability|DR |Immune|Resist|SR)(.*?))(Weakness|OFFENSE)")]
[H: WeaknessesSearch = strfind(statblock, "(?i)(Weaknesse?s?(.*?))(DEFENSE|OFFENSE)")]
[H: SpeedSearch = strfind(statblock, "(?i)(Speed (.+?))(Melee|Ranged|Tactic|Space|Reach|Special|Ranged|Stat|Spell)")]
[H: MeleeSearch = strfind(statblock, "(?i)(Melee (.+?))(Tactic|Space|Reach|Special|Ranged|Stat|Spell)")]
[H: RangedSearch = strfind(statblock, "(?i)(Ranged (.+?))(Tactic|Space|Reach|Special|Stat|Spell)")]
[H: SpaceReachSearch = strfind(statblock, "(?i)((Space|Reach)(.*?))(Tactic|Special|Stat|Spell)")]
[H: SpecialAttackSearch = strfind(statblock, "(?i)(Special Attacks?(.*?))(Tactic|Stat|Spell)")]
[H: SpellLikeSearch = strfind(statblock,  "(?i)((Arcane School |Gnome |Domain |Bloodline |Hellknight |Transmuter |Paladin |Cleric |Conjurer |Enchanter |Evoker |Necromancer |Arcane )?Spell-Like Abilities (.*?))(?=(Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Arcane School|Gnome|Domain|Bloodline|Hellknight|Arcane|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter|Tactic|Face|Stat|Spell))")]
[H: SpellsKnownSearch = strfind(statblock, "(?i)((Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter)? ?Spells Known(.*?))(?=(Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter|Tactic|Stat|Spells))")]
[H: SpellsPreparedSearch = strfind(statblock, "(?i)((Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter)? ?Spells Prepared(.*?))(?=(Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter|Tactic|Stat|Spells))")]
[H: TacticsSearch = strfind(statblock, "(?i)TACTICS(.*?)(?<!base )(?:STATISTICS)")]
[H: StatisticsSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Str (.*?))Base Atk")]
[H: BaseAtkSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Base Atk (.*?))(Feats|Skills|Languages|SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: FeatsSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Feats (.*?))(Skills|Languages|SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: SkillsSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Skills (.*?))(Languages|SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: LanguagesSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Languages? (.*?))(SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: SQSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(SQ (.*?))(Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: GearSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?((Combat )?Gear (.*?))(BASE STATISTICS|SPECIAL|ECOLOGY) ")]
[H: BaseStatisticsSearch = strfind(statblock, "(?i)(Base Statistics.*?)(STATISTICS.*?Str [0-9]|SPECIAL ABILITIES|ECOLOGY)")]
[H: SpecialAbilitiesSearch = strfind(statblock, "(?i)(SPECIAL ABILITIES)(.*?)(ECOLOGY)")]

<!-- If the line or section exists, put it in a variable and give it a line break before it -->
[H, IF(0<getFindCount(GenderEtcSearch)), CODE: {
	[GenderEtcNote = "
" + trim(getGroup(GenderEtcSearch, 1, 1))]
};{
	[GenderEtcNote = ""]
}]

[H, IF(0<getFindCount(AuraSearch)), CODE: {
	[AuraNote = "
" + trim(getGroup(AuraSearch, 1, 1))]
};{
	[AuraNote = ""]
}]

[H, IF(0<getFindCount(DefensiveAbilitiesSearch)), CODE: {
	[DANote = "
" + trim(getGroup(DefensiveAbilitiesSearch, 1, 1))]
};{
	[DANote = ""]
}]

[H, IF(0<getFindCount(WeaknessesSearch)), CODE: {
	[WeaknessNote = "
" + trim(getGroup(WeaknessesSearch, 1, 1))]
};{
	[WeaknessNote = ""]
}]

[H, IF(0<getFindCount(MeleeSearch)), CODE: {
	[MeleeNote = "
" + trim(getGroup(MeleeSearch, 1, 1))]
};{
	[MeleeNote = ""]
}]

[H, IF(0<getFindCount(RangedSearch)), CODE: {
	[RangedNote = "
" + trim(getGroup(RangedSearch, 1, 1))]
};{
	[RangedNote = ""]
}]

[H, IF(0<getFindCount(SpaceReachSearch)), CODE: {
	[SpaceReachNote = "
" + trim(getGroup(SpaceReachSearch, 1, 1))]
};{
	[SpaceReachNote = ""]
}]

[H, IF(0<getFindCount(SpecialAttackSearch)), CODE: {
	[SANote = "
" + trim(getGroup(SpecialAttackSearch, 1, 1))]
};{
	[SANote = ""]
}]

<!-- If a Spell-Like Abilities section exists, break it up into subsections based on constant/at will/or # per day. Multiple Spell-Like sections may exist from different sources, so create a section for each -->
[H: SLNote = ""]
[H, IF(0<getFindCount(SpellLikeSearch)), FOR(NumSpellLikeTypes, 1, getFindCount(SpellLikeSearch)+1), CODE: {
	[SpellLikeString = getGroup(SpellLikeSearch, NumSpellLikeTypes, 1)]
	[SpellLikeHeaderSearch = strfind(SpellLikeString, "(?i)(.*?)(Constant|At[ -]Will|[0-9]+/day)")]
	[ConstantSearch = strfind(SpellLikeString, "(?i)((Constant)(.*?))(At[ -]Will|[0-9]+/day)")]
	[IF(1>getFindCount(ConstantSearch)): ConstantSearch = strfind(SpellLikeString, "(?i)((Constant)(.*))")]
	[AtWillSearch = strfind(SpellLikeString, "(?i)((At[ -]Will)(.*?))([0-9]+/day)")]
	[IF(1>getFindCount(AtWillSearch)): AtWillSearch = strfind(SpellLikeString, "(?i)((At[ -]Will)(.*))")]
	[PerDayStringSearch = strfind(SpellLikeString, "(?i)(([0-9]+/day)(.*))")]
	[IF(0<getFindCount(PerDayStringSearch)): PerDayString = getGroup(PerDayStringSearch, 1, 1); PerDayString = ""]
	[PerDayTypesSearch = strfind(PerDayString, "(?i)(([0-9]+/day)(.*?))(?=([0-9]+/day))")]
	[PerDayAbilities = ""]
	[IF(0<getFindCount(PerDayTypesSearch)), FOR(DifferentPerDays, 1, getFindCount(PerDayTypesSearch)+1), CODE: {
		[PerDayString = replace(PerDayString, "(?i)([0-9]+/day(.*?))(?=([0-9]+/day))", "")]
		[ThisPerDay = trim(getGroup(PerDayTypesSearch, DifferentPerDays, 1))]
		[PerDayAbilities = PerDayAbilities + "
	" + ThisPerDay]
	}]
	[IF(PerDayString != ""): PerDayAbilities = PerDayAbilities + "
	" + trim(PerDayString)]
	[IF(0<getFindCount(SpellLikeHeaderSearch)): SpellLikeHeader = "

" + trim(getGroup(SpellLikeHeaderSearch, 1, 1)); SpellLikeHeader = "" ]
	[IF(0<getFindCount(ConstantSearch)), CODE: {
		[ConstantAbilities = "
	" + trim(getGroup(ConstantSearch, 1, 1))]
	};{
		[ConstantAbilities = ""]
	}]
	[IF(0<getFindCount(AtWillSearch)), CODE: {
		[AtWillAbilities = "
	" + trim(getGroup(AtWillSearch, 1, 1))]
	};{
		[AtWillAbilities = ""]
	}]
	[SLNote = SLNote + SpellLikeHeader + ConstantAbilities + AtWillAbilities + PerDayAbilities]
};{
	[SLNote = ""]
}]

<!-- If a Spells Known section exists, break it up into subsections based on spell level. Multiple Spells Known sections may exist from different sources, so create a section for each -->
[SKNote = ""]
[H, IF(0<getFindCount(SpellsKnownSearch)), FOR(NumSpellClasses, 1, getFindCount(SpellsKnownSearch)+1), CODE: {
	[ThisSpellClass = getGroup(SpellsKnownSearch, NumSpellClasses, 1)]
	[SpellsKnownHeaderSearch = strfind(ThisSpellClass, "(?i)(.*?\\)) +(9th|8th|7th|6th|5th|4th|3rd|2nd|1st|0 \\(|0th \\()")]
	[NinthLevelSearch = strfind(ThisSpellClass, "(?i)((9th)(.*?))(8th)")]
	[EighthLevelSearch = strfind(ThisSpellClass, "(?i)((8th)(.*?))(7th)")]
	[SeventhLevelSearch = strfind(ThisSpellClass, "(?i)((7th)(.*?))(6th)")]
	[SixthLevelSearch = strfind(ThisSpellClass, "(?i)((6th)(.*?))(5th)")]
	[FifthLevelSearch = strfind(ThisSpellClass, "(?i)((5th)(.*?))(4th)")]
	[FourthLevelSearch = strfind(ThisSpellClass, "(?i)((4th)(.*?))(3rd)")]
	[ThirdLevelSearch = strfind(ThisSpellClass, "(?i)((3rd)(.*?))(2nd)")]
	[SecondLevelSearch = strfind(ThisSpellClass, "(?i)((2nd)(.*?))(1st)")]
	[FirstLevelSearch = strfind(ThisSpellClass, "(?i)((1st)(.*?))(0 \\(|0th \\()")]
	[ThisClassSpecialSearch = strfind(ThisSpellClass, "(?i)((D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)(.*))")]
	[IF(0<getFindCount(ThisClassSpecialSearch)): ZerothLevelSearch = strfind(ThisSpellClass, "(?i)((0 \\(|0th \\()(.*?))(D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)"); ZerothLevelSearch = strfind(ThisSpellClass, "((0 \\(|0th \\()(.*))")]
	[IF(0<getFindCount(SpellsKnownHeaderSearch)): SpellsKnownHeader = "

" + trim(getGroup(SpellsKnownHeaderSearch, 1, 1)); SpellsKnownHeader = ""]
	[IF(0<getFindCount(NinthLevelSearch)), CODE: {
		[NinthLevel = "
	" + trim(getGroup(NinthLevelSearch, 1, 1))]
	};{
		[NinthLevel = ""]
	}]
	[IF(0<getFindCount(EighthLevelSearch)), CODE: {
		[EighthLevel = "
	" + trim(getGroup(EighthLevelSearch, 1, 1))]
	};{
		[EighthLevel = ""]
	}]
	[IF(0<getFindCount(SeventhLevelSearch)), CODE: {
		[SeventhLevel = "
	" + trim(getGroup(SeventhLevelSearch, 1, 1))]
	};{
		[SeventhLevel = ""]
	}]
	[IF(0<getFindCount(SixthLevelSearch)), CODE: {
		[SixthLevel = "
	" + trim(getGroup(SixthLevelSearch, 1, 1))]
	};{
		[SixthLevel = ""]
	}]
	[IF(0<getFindCount(FifthLevelSearch)), CODE: {
		[FifthLevel = "
	" + trim(getGroup(FifthLevelSearch, 1, 1))]
	};{
		[FifthLevel = ""]
	}]
	[IF(0<getFindCount(FourthLevelSearch)), CODE: {
		[FourthLevel = "
	" + trim(getGroup(FourthLevelSearch, 1, 1))]
	};{
		[FourthLevel = ""]
	}]
	[IF(0<getFindCount(ThirdLevelSearch)), CODE: {
		[ThirdLevel = "
	" + trim(getGroup(ThirdLevelSearch, 1, 1))]
	};{
		[ThirdLevel = ""]
	}]
	[IF(0<getFindCount(SecondLevelSearch)), CODE: {
		[SecondLevel = "
	" + trim(getGroup(SecondLevelSearch, 1, 1))]
	};{
		[SecondLevel = ""]
	}]
	[IF(0<getFindCount(FirstLevelSearch)), CODE: {
		[FirstLevel = "
	" + trim(getGroup(FirstLevelSearch, 1, 1))]
	};{
		[FirstLevel = ""]
	}]
	[IF(0<getFindCount(ZerothLevelSearch)), CODE: {
		[ZerothLevel = "
	" + trim(getGroup(ZerothLevelSearch, 1, 1))]
	};{
		[ZerothLevel = ""]
	}]
	[IF(0<getFindCount(ThisClassSpecialSearch)), CODE: {
		[ThisClassSpecial = "
	" + trim(getGroup(ThisClassSpecialSearch, 1, 1))]
	};{
		[ThisClassSpecial = ""]
	}]
	[SKNote = SKNote + SpellsKnownHeader + NinthLevel + EighthLevel + SeventhLevel + SixthLevel + FifthLevel + FourthLevel + ThirdLevel + SecondLevel + FirstLevel + ZerothLevel + ThisClassSpecial]
};{
	[SKNote = ""]
}]

<!-- If a Spells Prepared section exists, break it up into subsections based on spell level. Multiple Spells Prepared sections may exist from different sources, so create a section for each -->
[H: SPNote = ""]
[H, IF(0<getFindCount(SpellsPreparedSearch)), FOR(NumSpellClasses, 1, getFindCount(SpellsPreparedSearch)+1), CODE: {
	[ThisSpellClass = getGroup(SpellsPreparedSearch, NumSpellClasses, 1)]
	[SpellsPreparedHeaderSearch = strfind(ThisSpellClass, "(?i)(.*?\\)) +(9th|8th|7th|6th|5th|4th|3rd|2nd|1st|0 \\(|0th \\))")]
	[NinthLevelSearch = strfind(ThisSpellClass, "(?i)((9th)(.*?))(8th)")]
	[EighthLevelSearch = strfind(ThisSpellClass, "(?i)((8th)(.*?))(7th)")]
	[SeventhLevelSearch = strfind(ThisSpellClass, "(?i)((7th)(.*?))(6th)")]
	[SixthLevelSearch = strfind(ThisSpellClass, "(?i)((6th)(.*?))(5th)")]
	[FifthLevelSearch = strfind(ThisSpellClass, "(?i)((5th)(.*?))(4th)")]
	[FourthLevelSearch = strfind(ThisSpellClass, "(?i)((4th)(.*?))(3rd)")]
	[ThirdLevelSearch = strfind(ThisSpellClass, "(?i)((3rd)(.*?))(2nd)")]
	[SecondLevelSearch = strfind(ThisSpellClass, "(?i)((2nd)(.*?))(1st)")]
	[FirstLevelSearch = strfind(ThisSpellClass, "(?i)((1st)(.*?))(0 \\(|0th \\()")]
	[ThisClassSpecialSearch = strfind(ThisSpellClass, "(?i)((D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)(.*))")]
	[IF(0<getFindCount(ThisClassSpecialSearch)): ZerothLevelSearch = strfind(ThisSpellClass, "(?i)((0 \\(|0th \\()(.*?))(D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)"); ZerothLevelSearch = strfind(ThisSpellClass, "((0 \\(|0th \\()(.*))")]
	[IF(0<getFindCount(SpellsPreparedHeaderSearch)): SpellsPreparedHeader = "

" + trim(getGroup(SpellsPreparedHeaderSearch, 1, 1)); SpellsPreparedHeader = ""]
	[IF(0<getFindCount(NinthLevelSearch)), CODE: {
		[NinthLevel = "
	" + trim(getGroup(NinthLevelSearch, 1, 1))]
	};{
		[NinthLevel = ""]
	}]
	[IF(0<getFindCount(EighthLevelSearch)), CODE: {
		[EighthLevel = "
	" + trim(getGroup(EighthLevelSearch, 1, 1))]
	};{
		[EighthLevel = ""]
	}]
	[IF(0<getFindCount(SeventhLevelSearch)), CODE: {
		[SeventhLevel = "
	" + trim(getGroup(SeventhLevelSearch, 1, 1))]
	};{
		[SeventhLevel = ""]
	}]
	[IF(0<getFindCount(SixthLevelSearch)), CODE: {
		[SixthLevel = "
	" + trim(getGroup(SixthLevelSearch, 1, 1))]
	};{
		[SixthLevel = ""]
	}]
	[IF(0<getFindCount(FifthLevelSearch)), CODE: {
		[FifthLevel = "
	" + trim(getGroup(FifthLevelSearch, 1, 1))]
	};{
		[FifthLevel = ""]
	}]
	[IF(0<getFindCount(FourthLevelSearch)), CODE: {
		[FourthLevel = "
	" + trim(getGroup(FourthLevelSearch, 1, 1))]
	};{
		[FourthLevel = ""]
	}]
	[IF(0<getFindCount(ThirdLevelSearch)), CODE: {
		[ThirdLevel = "
	" + trim(getGroup(ThirdLevelSearch, 1, 1))]
	};{
		[ThirdLevel = ""]
	}]
	[IF(0<getFindCount(SecondLevelSearch)), CODE: {
		[SecondLevel = "
	" + trim(getGroup(SecondLevelSearch, 1, 1))]
	};{
		[SecondLevel = ""]
	}]
	[IF(0<getFindCount(FirstLevelSearch)), CODE: {
		[FirstLevel = "
	" + trim(getGroup(FirstLevelSearch, 1, 1))]
	};{
		[FirstLevel = ""]
	}]
	[IF(0<getFindCount(ZerothLevelSearch)), CODE: {
		[ZerothLevel = "
	" + trim(getGroup(ZerothLevelSearch, 1, 1))]
	};{
		[ZerothLevel = ""]
	}]
	[IF(0<getFindCount(ThisClassSpecialSearch)), CODE: {
		[ThisClassSpecial = "
	" + trim(getGroup(ThisClassSpecialSearch, 1, 1))]
	};{
		[ThisClassSpecial = ""]
	}]
	[SPNote = SPNote + SpellsPreparedHeader + NinthLevel + EighthLevel + SeventhLevel + SixthLevel + FifthLevel + FourthLevel + ThirdLevel + SecondLevel + FirstLevel + ZerothLevel + ThisClassSpecial]
};{
	[SPNote = ""]
}]

<!-- If a tactics section exists, break it up into subsections of Before Combat, During Combat, and Morale. The Base Statistics section, if it exists, may be in Tactics or it may be at the end of the Statistics section. In either case, put it in Tactics -->
[H, IF(0<getFindCount(TacticsSearch)), CODE: {
	[TacticsNote = "

-----TACTICS-----"]
	[TacticsString = getGroup(TacticsSearch, 1, 1)]
	[TacticsBeforeCombatSearch = strfind(TacticsString, "(?i)(Before Combat.*?)(During Combat|Morale|Base Statistics)")]
	[IF(1>getFindCount(TacticsBeforeCombatSearch)): TacticsBeforeCombatSearch = strfind(TacticsString, "(?i)(Before Combat.*)")]
	[IF(0<getFindCount(TacticsBeforeCombatSearch)): TacticsNote = TacticsNote + "
" + trim(getGroup(TacticsBeforeCombatSearch, 1, 1))]
	[TacticsDuringCombatSearch = strfind(TacticsString, "(?i)(During Combat.*?)(Morale|Base Statistics)")]
	[IF(1>getFindCount(TacticsDuringCombatSearch)): TacticsDuringCombatSearch = strfind(TacticsString, "(?i)(During Combat.*)")]
	[IF(0<getFindCount(TacticsDuringCombatSearch)): TacticsNote = TacticsNote + "
" + trim(getGroup(TacticsDuringCombatSearch, 1, 1))]
	[TacticsMoraleSearch = strfind(TacticsString, "(?i)(Morale.*?)(Base Statistics)")]
	[IF(1>getFindCount(TacticsMoraleSearch)): TacticsMoraleSearch = strfind(TacticsString, "(?i)(Morale.*)")]
	[IF(0<getFindCount(TacticsMoraleSearch)): TacticsNote = TacticsNote + "
" + trim(getGroup(TacticsMoraleSearch, 1, 1))]
};{
	[TacticsNote = ""]
}]
[H, IF(0<getFindCount(BaseStatisticsSearch)), CODE: {
	[IF(TacticsNote == ""), CODE: {
		[TacticsNote = "

-----TACTICS-----
" + trim(getGroup(BaseStatisticsSearch, 1, 1)))]
	};{
		[TacticsNote = TacticsNote + "
" + trim(getGroup(BaseStatisticsSearch, 1, 1)))]
	}]
}]

[H, IF(0<getFindCount(FeatsSearch)), CODE: {
	[FeatsNote = "
" + trim(getGroup(FeatsSearch, 1, 2))]
};{
	[FeatsNote = ""]
}]

[H, IF(0<getFindCount(SkillsSearch)), CODE: {
	[SkillsNote = "
" + trim(getGroup(SkillsSearch, 1, 2))]
};{
	[SkillsNote = ""]
}]

[H, IF(0<getFindCount(LanguagesSearch)), CODE: {
	[LangNote = "
" + trim(getGroup(LanguagesSearch, 1, 2))]
};{
	[LangNote = ""]
}]

[H, IF(0<getFindCount(SQSearch)), CODE: {
	[SQNote = "
" + trim(getGroup(SQSearch, 1, 2))]
};{
	[SQNote = ""]
}]

[H, IF(0<getFindCount(GearSearch)), CODE: {
	[GearNote = "
" + trim(getGroup(GearSearch, 1, 2))]
};{
	[GearNote = ""]
}]

<!-- If Special Abilities section exists, break it up into a separate line for each ability with its description -->
[H, IF(0<getFindCount(SpecialAbilitiesSearch)), CODE: {
	[SAbNote = "

-----SPECIAL ABILITIES-----"]
	[SAList = ""]
	[SpecialAbilityString = getGroup(SpecialAbilitiesSearch, 1, 2)]
	[SingleSpecialAbilitySearch = strfind(SpecialAbilityString, "(?i)(.*?\\. +)(?=.*? \\((Ex|Su|Sp)\\))")]
	[IF(0<getFindCount(SingleSpecialAbilitySearch)), FOR(NumOfSpecialAbilities, 1, getFindCount(SingleSpecialAbilitySearch)+1), CODE: {
		[SingleSpecialAbility = getGroup(SingleSpecialAbilitySearch, NumOfSpecialAbilities, 1)]
		[SpecialAbilityString = replace(SpecialAbilityString, "(?i)(.*?\\. +)(?=.*? \\((Ex|Su|Sp)\\))", "")]
		[SearchingForExtraLines = strfind(SingleSpecialAbility, "(?i)\\((Ex|Su|Sp)\\)")]
		[IF(0<getFindCount(SearchingForExtraLines)): SAList = SAList + "
" + trim(SingleSpecialAbility) ; SAList = SAList + " " + trim(SingleSpecialAbility)]
	}]
	[SAList = SAList + "
" + trim(SpecialAbilityString)]
	[SAbNote = SAbNote + SAList]
};{
	[SAbNote = ""]
}]

<!-- If the Ecology section exists, break it up into lines for Environment, Organization, and Treasure -->
[H, IF(0<getFindCount(EcologySearch)), CODE: {
	[EcologyNote = "

-----ECOLOGY-----"]
	[EcologyString = trim(getGroup(EcologySearch, 1, 2))]
	[EnvironmentSearch = strfind(EcologyString, "(?i)(Environment.*?)(Organization|Treasure)")]
	[IF(0<getFindCount(EnvironmentSearch)): EcologyNote = EcologyNote + "
" + trim(getGroup(EnvironmentSearch, 1, 1))]
	[OrganizationSearch = strfind(EcologyString, "(?i)(Organization.*?)(Treasure)")]
	[IF(0<getFindCount(OrganizationSearch)): EcologyNote = EcologyNote + "
" + trim(getGroup(OrganizationSearch, 1, 1))]
	[TreasureSearch = strfind(EcologyString, "(?i)(Treasure.*)")]
	[IF(0<getFindCount(TreasureSearch)), CODE: {
		[TreasureTypeSearch = strfind(getGroup(TreasureSearch, 1, 1), "(none|standard|double|incidental|NPC gear)")]
		[IF(0<getFindCount(TreasureTypeSearch)): TreasureType = getGroup(TreasureTypeSearch, 1, 1)]
	}]
	[IF(0<getFindCount(TreasureSearch)): EcologyNote = EcologyNote + "
" + trim(getGroup(TreasureSearch, 1, 1))]
};{
	[EcologyNote = ""]
}]

<!--Put it all together in the GM notes -->
[H: setGMNotes(trim(getGroup(NameSearch, 1, 1)) + "	" + trim(getGroup(CRSearch, 1, 1)) + "
------------
" + trim(getGroup(XPSearch, 1, 1)) +
 GenderEtcNote + "
" + trim(getGroup(AlignmentSizeRaceSearch, 1, 1)) + "
" + trim(getGroup(InitSensesSearch, 1, 1)) + 
 AuraNote + "

-----DEFENSE-----
" + trim(getGroup(ACStringSearch, 1, 1)) + "
" + trim(getGroup(HPStringSearch, 1, 1)) + "
" + trim(getGroup(SavesSearch, 1, 1)) + 
 DANote + 
 WeaknessNote + "

-----OFFENSE-----
" + trim(getGroup(SpeedSearch, 1, 1)) +
 MeleeNote +
 RangedNote +
 SpaceReachNote +
 SANote +
 SLNote +
 SKNote +
 SPNote +
 TacticsNote + "

-----STATISTICS-----
" + trim(getGroup(StatisticsSearch, 1, 2)) + "
" + trim(getGroup(BaseAtkSearch, 1, 2)) + 
 FeatsNote +
 SkillsNote +
 LangNote +
 SQNote +
 GearNote +
 SAbNote +
 EcologyNote)]

[H: output="Token Updated:<br>"]
[H: id = strfind(statblock, "^(.+?)CR ?[0-9]")]
[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 ?)([0-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(0)]
	[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+", "]
}]

 <!-- Defensive Abilities Sample: Defensive Abilities incorporeal, channel resistance +2; -->
[H: DefensiveAbil = ""]
[H, IF(0<getFindCount(DefensiveAbilitiesSearch)),CODE: {
	[DefensiveAbilitiesString=getGroup(DefensiveAbilitiesSearch, 1, 1)]
	[DefensiveAloneSearch=strfind(DefensiveAbilitiesString, "(?i)Defensive (Abilities|Ability)(.*?); +(SR|Immune|Resist|DR)")]
	[IF(0==getFindCount(DefensiveAloneSearch)): DefensiveAloneSearch = strfind(DefensiveAbilitiesString, "(?i)Defensive (Ability|Abilities)(.*)")]
	[IF(0<getFindCount(DefensiveAloneSearch)), CODE: {
		[DefensiveAbil = trim(getGroup(DefensiveAloneSearch, 1, 2))]
		[output=output+"Defensive Abilities: "+DefensiveAbil+", "]
	}]
}]

<!-- Find Immunities. Sample: Immune acid, curse effects, flanking, mind-affecting effects, paralysis, poison, sleep -->
[H: Immune = ""]
[H: id = strfind(statblock, "(Immune )(([a-zA-Z]+[-]?,? ?)+);? +(Weakness|OFFENSE|Resist|SR)")]
[H, IF(0<getFindCount(id)),CODE: {
	[id2 = strfind(getGroup(id, 1, 2), "(.*) (Weakness|OFFENSE|[;]|Resist)")]
	[IF(0<getFindCount(id2)): Immune = getGroup(id2, 1, 1); Immune = getGroup(id, 1, 2)]
	[Immune = trim(Immune)]
	[output=output+"Immunities: "+Immune+", "]
 }]

<!-- Find Weaknesses. Sample: Weaknesses light sensitivity -->
[H: Weaknesses = ""]
[H: id = strfind(statblock, "(Weaknesse?s? )((\\w+-?,? ?)+) +(?i)(OFFENSE|DEFENSE)")]
[H, IF(0<getFindCount(id)),CODE: {
	[Weaknesses = trim(getGroup(id, 1, 2))]
	[output=output+"Weaknesses: "+Weaknesses+", "]
}]
  
<!-- Find DR. Sample: 1/-, 2/adamantine, 3/good -->
[h: DRSearch = ""]
[H: id = strfind(statblock, "DR(.?[0-9]+)/(.+?)[,;O]")]
[H, IF(0<getFindCount(id)), COUNT(getFindCount(id)), CODE: {
	[thisrollcount = roll.count+1]
	[thissearchDR = replace(getGroup(id, thisrollcount, 2), "Cold Iron", "ColdIron")]
	[drANDORsearch = strfind(thissearchDR, "(and|or) ([a-zA-Z]+ ?)")]
	[if(0<getFindCount(drANDORsearch)), COUNT(getFindCount(drANDORsearch)), CODE: {
	[DRSearch = trim(DRSearch + " " + getGroup(id, thisrollcount, 1)+"/"+getGroup(drANDORsearch, roll.count+1, 2))]
	}]
	[DRSearch = trim(DRSearch + " " + getGroup(id, thisrollcount, 1)+"/"+thissearchDR)]
	[DRSearch = replace(DRSearch, "/-", "/All")]
	[DRSearch = replace(DRSearch, "(?i)/Cold Iron", "/ColdIron")]
	[DRSearch = replace(DRSearch, "(?i)/Chaotic", "/Chaos")]
	[DRSearch = replace(DRSearch, "(?i)/Lawful", "/Law")]
}]
  
<!-- Find Resistences. Sample: Resist acid 5, cold 5, electricity 5 -->
[h: resistSearch = ""]
[H: id = strfind(statblock, "(\\bResist \\b(\\w+ \\d+,? ?)+)")]
[H, IF(0<getFindCount(id)),CODE: {
	[resistSearch = trim(getGroup(id, 1, 1))]
}]

<!-- Find SR -->  
[H: SRSearch = ""]
[H: id = strfind(statblock, "SR ([0-9]+)")]
[H, IF(0<getFindCount(id)),CODE: {
	[SRSearch=getGroup(id, 1, 1)]
}]

<!-- Put DR & Resist values into JSON Property DRER -->
[H, FOREACH( reduceTypes, json.fields( DRER ) ), CODE: {
 	[jRC = json.get( DRER, reduceTypes ) ]
 	[prefixT = substring( reduceTypes, 0, 1 ) ]
   

 	[FOREACH( reduceType, json.fields( jRC ) ), CODE: {
    	[preValue = json.get( jRC, reduceType ) ]
		[reduceVal = 0]

		[resistFind = strfind(resistSearch, "(?i)("+reduceType+" )(\\d+)")]

		 [IF(0<getFindCount(resistFind) && prefixT == "E"): reduceVal = getGroup(resistFind, 1, 2)]

		[drFind = strfind(DRSearch, "(?i)(\\d+)/("+reduceType+")")]
		[IF(0<getFindCount(drFind) && prefixT == "D"): reduceVal = getGroup(drFind, 1, 1)]
  
		[ chgValue = eval( "" + prefixT + reduceType + "=" + reduceVal ) ]
	  
    	[IF( reduceType != "Note" ): tmodV = json.get( DRERMod, if( reduceTypes == "Damage Reduction", "DR_", if( reduceTypes == "Energy Resistance", "ER_", if( reduceTypes == "Spell Resistance", "SR_", "EV_") ) ) + reduceType ) ]
    	[IF( reduceType != "Note" ): tVal = max( chgValue, tmodV ); tVal = chgValue ]
     
		[IF( tVal != 0 && tVal != "" && prefixT == "D"): DR = listAppend( DR, tVal + if( reduceType == "Note", "", "/" + if( reduceType == "All", "-", reduceType ) ) ) ]
	  
		[IF( tVal != 0 && tVal != "" && prefixT == "E"): DR = listAppend( DR, reduceType + if( reduceType == "Note", "", " (" + if( tVal == "999", "Immune", tVal ) + ")" ) ) ]
    	[IF( reduceType == "Note" && chgValue == 0 ): chgValue = "" ]
    	[IF( preValue != chgValue ):  output = output + reduceType + "= " + chgValue + " (" + preValue + "), "]
    	[IF( preValue != chgValue ): jRC = json.set( jRC, reduceType, chgValue ) ]

}]
 	[setProperty( "DRER", json.set( DRER, reduceTypes, jRC ) ) ]
   
 	[IF(DRSearch != "" || resistSearch != ""): output=output+"<br>"]
}]

[H, If(Immune != ""), CODE:{
	[jRC = json.get( DRER, "Energy Resistance" )]
	[jRCnew = json.set( jRC, "Note", "Immune: "+Immune)]
	[newDRER = json.set( DRER, "Energy Resistance", JRCnew)]
	[setProperty("DRER", newDRER)]
	[DR = listAppend(DR, "Immune: "+Immune)]
}]

[H, If(SRSearch != ""), CODE:{
	[jRC = json.get( DRER, "Spell Resistance" )]
	[jRCnew = json.set( jRC, "SR", SRSearch)]
	[newDRER = json.set( DRER, "Spell Resistance", JRCnew)]
	[setProperty("DRER", newDRER)]
	[DR = listAppend(DR, "SR: "+SRSearch)]
}]

[H: vulnerability =  strfind(Weaknesses, "(Vulnerability|Vulnerable|vulnerability|vulnerable) to ([a-zA-Z]*)")]
[H: vtypes = getFindCount(vulnerability)]
[H, IF(0<vtypes), CODE: {
	[COUNT(vtypes), CODE: {
		[jRC = json.get(DRER, "Energy Vulnerability")]
      [ tvuln = "V."+upper(lower(getGroup(vulnerability,roll.count+1,2)),1)]
      [ validVulnerability = listContains("V.Acid,V.Cold,V.Electricity,V.Fire,V.Sonic",tvuln)]
		[if(validVulnerability): jRCnew = json.set(jRC, tvuln, 1)]
		[if(validVulnerability): newDRER = json.set(DRER, "Energy Vulnerability", jRCnew)]
		[if(validVulnerability): setProperty("DRER", newDRER)]
		[if(validVulnerability): DR = listAppend(DR, tvuln)]
	}]
}]

<!-- STATS BEGIN -->
[H: id = strfind(statblock, "(?i)(?<!base )STATISTICS.*?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>"]
}]
[H: SavesString = getGroup(SavesSearch, 1, 1)]
[H: id = strfind(SavesString, ";(.*)")]
[H, IF(0<getFindCount(id)), CODE: {
	[SaveMisc = trim(getGroup(id, 1, 1))]
	[output=output+"Saves Misc Notes: "+SaveMisc+"<br>"]
}]

<!-- SAVES END -->

[H: id = strfind(statblock, "hp ([0-9]+).*?\\(([0-9]+ ?HD; ?)?([0-9d+]*)")]
[H: hpString = getGroup(id, 1, 3)]
[H: numOfEachHDType = strfind(hpString, "([0-9]+)d")]
[H: numOfHDTypes = getFindCount(numOfEachHDType)]
[H, IF(0<getFindCount(id)),CODE: {
	[H: HD = 0]
	[H, COUNT(numOfHDTypes), CODE:
	{
		[HD = HD + getGroup(numOfEachHDType, (roll.count+1), 1)]
	}]
	[HP=getGroup(id, 1, 1)]
	[Level = max(1,HD)]
	[HPmax="[R: max(Level, ( "+(HP-(ConB*Level))+" + (ConB * Level) ) )]"]
	[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-natural]
	[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)]
	[Movement = spd]
}; {
	[Speed = json.set(Speed, "base",0)]
	[Movement = 0]
}]
[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)]
	[Movement = getProperty("Movement") + ", Climb:" + 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)]
	[Movement = getProperty("Movement") + ", 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)]
	[Movement = getProperty("Movement") + ", Burrow:" + spd]
}]
[H: id = strfind(statblock, "fly ([0-9]+) ft\\.? ?\\(?([a-zA-Z]+)?\\)? ?(Melee|Ranged|Special|Spell)")]
[H, IF(0<getFindCount(id)), CODE: {
	[spd=getGroup(id, 1, 1)]
	[output=output+", Fly: "+spd]
	[Speed = json.set(Speed, "fly",spd)]
	[flyman=lower(getGroup(id, 1, 2))]
	[SWITCH(flyman), CODE:
		case "": {
			[flyman = "average"]
			[Speed = json.set(Speed, "flymaneuver",4)]
		};
		case "clumsy": {
			[flyman = "Clumsy"]
			[Speed = json.set(Speed, "flymaneuver",2)]
		};
		case "poor": {
			[flyman = "Poor"]
			[Speed = json.set(Speed, "flymaneuver",3)]
		};
		case "average": {
			[flyman = "Average"]
			[Speed = json.set(Speed, "flymaneuver",4)]
		};
		case "good": {
			[flyman = "Good"]
			[Speed = json.set(Speed, "flymaneuver",5)]
		};
		case "perfect": {
			[flyman = "Perfect"]
			[Speed = json.set(Speed, "flymaneuver",6)]
		};
	]
	[Movement = getProperty("Movement") + ", Fly:" + spd + " (" + flyman + ")"]
}]
[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 -->

<!--Languages Begin-->
[H, IF(0<getFindCount(LanguagesSearch)), CODE: {
	[LanguageList = strfind(getGroup(LanguagesSearch, 1, 2), "Languages?(.*)")]
	[IF(0<getFindCount(LanguageList)): Race = json.set(Race, "notes", trim(getGroup(LanguageList, 1, 1)))]
}]
<!--Languages End-->

<!-- SQ on popup begin -->
[H, IF(0<getFindCount(SQSearch)), CODE: {
	[SpecialQualWithoutSQ = strfind(getGroup(SQSearch, 1, 2), "SQ (.*)")]
	[SpecialQual = trim(getGroup(SpecialQualWithoutSQ, 1, 1))]
}]
<!--SQ on popup end-->

<!-- FEATS BEGIN -->
[H: output=output+"Feats: "]
[H, IF(0<getFindCount(FeatsSearch)), CODE: {
   [ tFeats = replace(getGroup(FeatsSearch, 1, 2), "Feats ", "", 1) ]
   [ tFeats = trim(replace(tFeats, "B,", ",")) ]
   [ tFEflag = endsWith(tFeats, "B") ]
   [ IF(tFEflag): tFeats = substring(tFeats, 0, length(tFeats)-1); "" ]
   [ tFeatsJ = json.fromList(tFeats) ]
   [ tFeatsJ2 = "[]" ]
   [ tFeatsJparam = "{}" ]
   [ Foreach(p, tFeatsJ), CODE: {
      [ tidxp = indexOf(p,"(") ]
      [ IF( tidxp < 0 ): tpm = trim(p); tpm = trim(substring(p,0,tidxp)) ]
      [ IF( tidxp < 0 ): tpsub = ""; tpsub = substring(p,tidxp+1,length(p)-1) ]
      [ tFeatsJ2 = json.append(tFeatsJ2, tpm) ]
      [ IF( tidxp < 0 ): "";  tFeatsJparam = json.set(tFeatsJparam, tpm, tpsub) ]
   }]
   [ tFeatsJ2 = json.sort(tFeatsJ2) ]
   [ setProperty("FeatsJ", tFeatsJ2) ]
   [ setProperty("FeatsJparam", tFeatsJparam) ]
   
   [H: sysVars = table("SysVars", 0)]
   [H: sysFeats = table("SysVars", json.get(sysVars, "sysFeats"))]
   [H: tsfl = json.fields(sysFeats,"json")]
   [H: tFeatsSys = getProperty("Feats")]
   [H, FOREACH(p,tsfl),CODE: {
      [H: tfcont = json.contains(tFeatsJ2,p)]
      [H: tval = if(tfcont,1,0)]
      [H: tFeatsSys = setStrProp(tFeatsSys,json.get(sysFeats,p),tval)]
   }]
   [H: setProperty("Feats", tFeatsSys)]
   [H: output=output+tFeats ]
};{
}]
[H: output=output+"<br>"]
<!-- FEATS END -->

<!-- Ferocity for Orcs works just like Die Hard -->
[H: id = strfind(DefensiveAbil, "(?i)(ferocity)")]
[H, IF(0<getFindCount(id)), CODE: {
	[Feats=setStrProp(Feats, "DieHard",1)]
	[H: output=output+getGroup(id, 1, 1)+", "]
}]  
  
[H: output=output+"<br>"]

[H: id = strfind(statblock, "(?i)Special Attacks? (.*?)(Spells|Spell[ -]Like|Statistics|Tactics)")]
[H, IF(0<getFindCount(id)), CODE: {
	[SpecialATK = trim(getGroup(id, 1, 1))]
	[SpecialATK=substring(SpecialATK,0,min(length(SpecialATK),70))]
	[output=output+"SA: "+SpecialATK+"<br>"]
  };{
	[SpecialATK=""]
}]

[H: id = strfind(statblock, "(Fast Healing )([0-9]+)")]
[H, IF(0<getFindCount(id)),CODE: {
	[FastHealing=getGroup(id, 1, 2)]
	[output=output+"Fast Healing: "+FastHealing+"<br>"]
}]

<!-- Setting Regeneration and Anti Regen Damage Type if present -->
[H: id = strfind(statblock, "(Regeneration )([0-9]+) ([(][a-zA-Z]+[)])?")]
[H, IF(0<getFindCount(id)),CODE: {
	[Regeneration=getGroup(id, 1, 2)]
	[output=output+"Regeneration: "+Regeneration+"<br>"]
	[AntiRegen=if(getGroup(id, 1, 3)=="", "", getGroup(id, 1, 3))]
	[output=output+"Regeneration: "+Regeneration+"<br>"+if(AntiRegen=="", "", "Anti-Regen Damage Type: "+AntiRegen+"<br>")]
}]
  
<!-- ATTACKS BEGIN -->
<!-- Insert empty weapon strings -->
[H: wpnstr=getPropertyDefault( "Weapon0" )]
[H, c(17,""),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|Space|Reach|Special|Ranged|Spell-Like|Spells|Statistic)")]
[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=replace(atkstr, "((\\(.+\\)) and )", "\$2, ")]
	[atkstr=stringToList(atkstr, "(, )",":")]
	[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 (.+?)(?i)(Tactic|Space|Reach|Special|Spell-Like|Spells|Statistic)")]
[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+)(/[+-]\\d+)?( [a-zA-Z]* )?[/+\\-0-9\\s]*?\\((.+)\\)")]
	[IF(0<getFindCount(id)), CODE: {
	[WpnName = "Weapon" + wpnno]

    <!-- Quantity / Weapon Bonus-->
  	[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)+ "'")]
	
		<!-- Weapon Name -->
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"Name", replace(wbonus, " ", "") + if(wbonus=="", "", " ") + getGroup(id, 1, 3) + if(getGroup(id, 1, 6)=="", "", " ("+trim(getGroup(id, 1, 6))+")")) + "'")]
    
	<!-- Manufactured ? -->
  	[id2 = strfind(atkstr, "(?i)(slam|bite|claw|gore|hoof|tentacle|wing|pincer|tail|sting|talon|Constrict)")]
  	[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-SizeM;watkbonus=watkbonus-BAB-StrB-SizeM]
  	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"AtkBonus", watkbonus)+ "'")]

	<!-- CritMult-->
  	[wdam=getGroup(id, 1, 7)]
  	[CritMult=2]
  	[id2 = strfind(wdam, "([0-9]+d[0-9]+)?([+-][0-9]+)?/?([0-9]+-?[0-9]+)?([xX]([0-9]))? ?([a-zA-Z0-9 ]+)?")]
  	[IF(getGroup(id2, 1, 5)==""): CritMult=2; CritMult=replace(getGroup(id2, 1, 5),"[xX]", "")]
  	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"CritMult", CritMult)+ "'")]

	<!-- CritRange-->
  	[CritRange=20]
  	[critFromWdam = getGroup(id2, 1, 3)]
	[critFromWdamsearch = strfind(critFromWdam, "([0-9]+)-20")]
	[IF(0<getFindCount(critFromWdamsearch)): CritRange = getGroup(critFromWdamsearch, 1, 1)]
  	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"CritRange", CritRange)+ "'")]
	
	<!-- CritExtraDamage-->
  	[DmgExtraCrit=""]

	<!-- Damage -->
  	[Damage=if(getGroup(id2, 1, 1) == "", "0", getGroup(id2, 1, 1))]
	[DmgExtra = 0]
	[DmgExtraName = ""]
	[newPlusStr= ""]
	[DamagePlus = ""]
	[DmgExtraCrit = "0d10"]
	[DamagePlus = if(0<getFindCount(id2), getGroup(id2, 1, 6), "")]
	[DmgBonus = 0]
	[DmgBonus = if(0<getFindCount(id2), getGroup(id2, 1, 2), 0)]
  	[id2 = strfind(atkstr, "(?i)(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)")]
	[DmgBonus = if(DmgBonus == "", 0, DmgBonus)]
  	[IF(0<getFindCount(id2)):eval(WpnName + "= '" + setStrProp(eval(WpnName),"TwoHanded", 1)+ "'")]
	[DmgBonus = round(if(0<getFindCount(id2), DmgBonus-Str2hB, DmgBonus-StrB))]
	
  	[ExtraDamageSearch = strfind(DamagePlus, "(.*) ([0-9]+[d0-9]+?) ?([a-zA-Z]+) ?(.*)")]
	[IF(0<getFindCount(ExtraDamageSearch)):DmgExtra=getGroup(ExtraDamageSearch, 1, 2)]
	[IF(0<getFindCount(ExtraDamageSearch)):DmgExtraName=getGroup(ExtraDamageSearch, 1, 3)]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = getGroup(ExtraDamageSearch, 1, 1) + getGroup(ExtraDamageSearch, 1, 4)]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, "plus", "")]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, "and", "")]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = trim(newPlusStr)]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, "  ", " ")]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, " ", " plus ")]
	[IF(0<getFindCount(ExtraDamageSearch)):Damage = trim(Damage + if(DmgBonus<0, "", "+") + DmgBonus + " " + IF(newPlusStr != "", "plus " + newPlusStr, newPlusStr)); Damage = trim(Damage + if(DmgBonus<0, "", "+") + DmgBonus + " " + DamagePlus)]
	[burstsearch = strfind(getGroup(id, 1, 3), "([a-zA-Z]+ )([Bb]urst)")]
	[IF(0<getFindCount(burstsearch)): DmgExtraCrit = "1d10 " + trim(getGroup(burstsearch, 1, 1))]
	
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"Damage", Damage)+ "'")]
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"DmgExtra", DmgExtra)+ "'")]
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"DmgExtraName", DmgExtraName)+ "'")]
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"DmgExtraCrit", DmgExtraCrit)+ "'")]
	<!-- Two handed ? -->
    <!-- Default to onehanded weapon -->
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"TwoHanded", 0)+ "'")]

}]
  
	[wpnno = wpnno +1]
}]

[H: broadcast(strformat("Import Complete %{name}"),"self,gm")]

Elorebaen
Dragon
Posts: 365
Joined: Sat Dec 22, 2007 5:37 pm

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by Elorebaen »

Thank you for the update!!!

User avatar
JamzTheMan
Great Wyrm
Posts: 1872
Joined: Mon May 10, 2010 12:59 pm
Location: Chicagoland
Contact:

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by JamzTheMan »

aliasmask wrote:I made some changes, but it's not very "drop-in" oriented. I added a bunch of state images and changed the code to handle more type of movement like swim and burrow. I used another contributors images from this site and tweaked them slightly and added my own numbering. There's no easy way to import the images other than doing it one at a time. I'll post some images to help you install it for yourself.

You'll need to add the states as corner image, upper right under the Elevations group name.
||| CODE |||

Code: Select all

@@ @changeElevation
<!-- changeElevation(): output -->
[H: elevation.start = getProperty("Elevation")]
[H: elevation.states = getProperty("elevation.states")]
[H: elevation.type = getProperty("elevation.type")]
[H, if(! isNumber(elevation.start)): elevation.value = 0; elevation.value = elevation.start]
[H, if(! isNumber(elevation.states)): elevation.states = 0]

[H: elevationTypes = ",Fly,Levitate,Climb,Burrow,Swim,Above,Below"]
[H: type.index = max(0,listFind(elevationTypes,elevation.type))]

[H: abort(input("tip|<html><b><i>Either set current elevation or put adjustment value in.</i></b></html>||LABEL|SPAN=TRUE",
   strformat('elevation.input|%{elevation.value}|Current Elevation|TEXT'),
   "tip|<html><b>* <i>Fly, Levitate, Climb and Above are positive while the others appear as negative.</i></b></html>||LABEL|SPAN=TRUE",
   "elevation.adjustment|0|Adjust Elevation|TEXT",
   "tip|<html><b>* <i>Elevation Adjustment for Fly is halved going up and doubled going down.</i></b></html>||LABEL|SPAN=TRUE",
   strformat('elevation.type|%{elevationTypes}|Set Elevation Type|LIST|VALUE=STRING SELECT=%{type.index}')
))]

<!-- error checking -->
[H, if(! isNumber(elevation.input)): elevation.input = 0]
[H: elevation.value = elevation.input]
[H, if(! isNumber(elevation.adjustment)): elevation.adjustment = 0]

[H, if(elevation.type == ""): elevation.states = 0; elevation.states = 1]

<!-- get new elevation -->
[H, if( elevation.type == "Fly"), code: {
   [H, if(elevation.adjustment > 0): elevation.adjustment = floor(elevation.adjustment/10) * 5; elevation.adjustment = elevation.adjustment * 2]
};{}]


[H: elevation.new = elevation.value + elevation.adjustment]
[H, if(! elevation.states && elevation.new == 0): elevation.type = ""]

[H: limit = 0]
[H, if(listContains("Burrow,Below",elevation.type)): limit = -5]
[H, if(elevation.type == "Above"): limit = 5]

<!-- enforce 5ft increments -->
[H: elevation.new = floor(elevation.new/5)*5]
[H, if(listContains("Burrow,Swim,Below",elevation.type)): elevation.new = min(limit,elevation.new); elevation.new = max(limit,elevation.new)]

<!-- clear elevation states -->
[H, for(i,5,155,5): setState(i,0)]
[H: newStates = getTokenStates("json","Elevations")]
[H, foreach(state,newStates): setState(state,0)]

<!-- set states -->
[H, if(elevation.states), code: {
   [H: setState("Elevation"+elevation.type,1)]
   <!-- convert elevation to state name -->
   [H: elevation.string = max(-95,min(995,elevation.new))]
   [H, if(elevation.string < 0): setState("negative",1)]
   [H: elevation.string = abs(elevation.string)]
   [H: elevation.100 = floor(elevation.string / 100)]
   [H: elevation.10 = floor((elevation.string - elevation.100 * 100)/10)]
   [H: elevation.1 = elevation.string - (elevation.100 * 100) - (elevation.10 * 10)]
   [H, if(elevation.100), code: {
      [H: setState("e"+elevation.100+"__",1)]
      [H: setState("e_"+elevation.10+"_",1)]
      [H: setState("e__"+elevation.1,1)]
   };{
      [H, if(elevation.10): setState("e_"+elevation.10+"_",1)]
      [H: setState("e__"+elevation.1,1)]
   }]   
};{}]

[H, if(! elevation.states && elevation.new == 0): elevation.type = ""]

<!-- set elevation value -->
[H: setProperty("Elevation",elevation.new)]
[H: setProperty("elevation.states",elevation.states)]
[H: setProperty("elevation.type",elevation.type)]

<!-- output elevation changes -->
[H: tokenImage = am.tokenImageLink(currentToken())]

[H: output = strformat('
   <table style="border-spacing:0px;border-style:solid;border-color:black;border-width:1pt;padding:0px">
      <tr>
         <td width="34" style="padding:0px">%{tokenImage}</td>
         <td width="250" style="background-color:aqua;padding:0px 5px 2px 5px;">%{token.name} (<b>%{elevation.type}</b>): Elevation changed from %{elevation.start} to %{elevation.new}.</td>
      </tr>
   </table>
')]

[H: am.play.output(output)]

!!
 
The attachment change elevation example.jpg is no longer available
||| IMAGES |||
The attachment state images.jpg is no longer available
The attachment Elevation States.jpg is no longer available
I haven't tested this, but you can probably omit the images as long as you don't choose the state type, like fly or swim. It'll just adjust the elevation value, but if you're going to do that, you may as well use the original code above.

Edit: Here is a link to my latest campaign with these changes: PF B89 Core2 5-31-14

In case you want it as well, here's the green number I did with a snippet of the code. That way you can easily tell if you are climbing up 200ft or down 200ft. Also put the plus/minus signs in until you reach >95.
green_elevations.zip
(35.43 KiB) Downloaded 146 times
Green states snippet

Code: Select all

<!-- set states -->
[H, if(elevation.states), code: {
   [H: setState("Elevation"+elevation.type,1)]
   
   <!-- convert elevation to state name -->
   [H: elevation.string = max(-995,min(995,elevation.new))]
   [H, if(elevation.string > -100 && elevation.string < 0): setState("negative",1)]
   [H, if(elevation.string > 0 && elevation.string < 100): setState("positive",1)]
   [H, if(elevation.string > 0): statePrefix="g"; statePrefix="e"]
   [H: elevation.string = abs(elevation.string)]
   [H: elevation.100 = floor(elevation.string / 100)]
   [H: elevation.10 = floor((elevation.string - elevation.100 * 100)/10)]
   [H: elevation.1 = elevation.string - (elevation.100 * 100) - (elevation.10 * 10)]
   [H, if(elevation.100), code: {
      [H: setState(statePrefix+elevation.100+"__",1)]
      [H: setState(statePrefix+"_"+elevation.10+"_",1)]
      [H: setState(statePrefix+"__"+elevation.1,1)]
   };{
      [H, if(elevation.10): setState(statePrefix+"_"+elevation.10+"_",1)]
      [H: setState(statePrefix+"__"+elevation.1,1)]
   }]   
};{}]
-Jamz
____________________
Custom MapTool 1.4.x.x Fork: maptool.nerps.net
Custom TokenTool 2.0 Fork: tokentool.nerps.net
More information here: MapTool Nerps! Fork

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

I don't worry too much about absolute elevations, just differences. It's only important when calculating range for the most part. So, -95 to 995 covers all my bases so far as display goes. The elevation is still set correctly if going beyond those points. But color is a good idea in showing positive and negative elevations and would extend the range.

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: [AM Macros] Lindsay's PF/3.5 Campaign FW

Post by aliasmask »

New update to statblock importer to fix statsheet variable input error when [] or {} appears in display text. I'm not sure if this fix breaks anything else that may use [] in the code. I did look through the other regex stuff and didn't find anything that referenced those characters directly.

Also see this post for the previous update: http://forums.rptools.net/viewtopic.php ... 19#p256632
Lib:libDnD35Pathfinder wrote:
||| pfStatBlockImport |||

Code: Select all

<!-- Statblock2Token v2.0.4 (3-15-15)
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
LGM: Corrected vulnerability, Added Feats
AM (v2.0.3): fixed hp each; name get; default sight off; valid ulnerability check; made UDF; output change 
AM (v2.0.4): fixed stat sheet input error (infinite loop on mouse over)
-->
[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 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)]
}]

<!-- 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, "Ecolo gy", "Ecology")]
[H: statblock = replace(statblock, "  ", " ")]

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

<!-- Start formatting imported statblock and set it to GM notes -->

<!-- If Ecology section comes before Special Abilities, cut it out and put it last. If the Ecology section doesn't exist, create a blank one last -->
[H: EcologyThenSAbSearch = strfind(statblock, "(?i)(ECOLOGY)(.*?)(Special Abilities)")]
[H, IF(0<getFindCount(EcologythenSAbSearch)), CODE: {
	[IF(getGroup(EcologyThenSAbSearch, 1, 3) == "special abilities" || getGroup(EcologyThenSAbSearch, 1, 3) == "Special Abilities" || getGroup(EcologyThenSAbSearch, 1, 3) == "Special abilities" || getGroup(EcologyThenSAbSearch, 1, 3) == "SPECIAL ABILITIES"), CODE: {
		[EcologyString = getGroup(EcologyThenSAbSearch, 1, 0)]
		[statblock = replace(statblock, "(?i)((ECOLOGY)(.*?))((Special Abilities)(.*))", "\$4  \$1" )]
		[EcologySearch = strfind(statblock, "(?i)(ECOLOGY)(.*)")]
	};{
	}]
};{
	[EcologySearch = strfind(statblock, "(?i)(ECOLOGY)(.*)")]
	[IF(0<getFindCount(EcologySearch)), CODE: {
		[statblock = statblock]
	};{
		[statblock = statblock + " ECOLOGY "]
		[EcologySearch = strfind(statblock, "(?i)(ECOLOGY)(.*)")]
	}]
}]

<!-- Search for lines and sections of statblock individually -->
[H: NameSearch = strfind(statblock, "(.*?)CR")]
[H: CRSearch = strfind(statblock, "(CR ?[0-9]+(/[0-9]+)?)")]
[H: XPSearch = strfind(statblock, "(XP [0-9,]+)")]
[H: GenderEtcSearch = strfind(statblock, "(?i)XP [0-9,]+ ([a-z ,0-9]*)( LG | LN | LE | NG | N | NE | CG | CN | CE )")]
[H: AlignmentSizeRaceSearch = strfind(statblock, "(?i)(( LG | LN | LE | NG | N | NE | CG | CN | CE ).*?)Init")]
[H: InitSensesSearch = strfind(statblock, "(?i)(Init.*?[0-9]) +(Aura|DEFENSE|Weakness)")]
[H: AuraSearch = strfind(statblock, "(?i)(Aura.*?) DEFENSE")]
[H: ACStringSearch = strfind(statblock, "(?i)(AC [0-9].*?\\)) +HP")]
[H: HPStringSearch = strfind(statblock, "(?i)((HP) .*?)Fort ")]
[H: SavesSearch = strfind(statblock, "(?i)(Fort (.*?))(Defensive Abilit|DR [0-9]|Immune|Resist|SR|Weakness|OFFENSE)")]
[H: DefensiveAbilitiesSearch = strfind(statblock, "(?i)((Defensive Abilities|Defensive Ability|DR |Immune|Resist|SR)(.*?))(Weakness|OFFENSE)")]
[H: WeaknessesSearch = strfind(statblock, "(?i)(Weaknesse?s?(.*?))(DEFENSE|OFFENSE)")]
[H: SpeedSearch = strfind(statblock, "(?i)(Speed (.+?))(Melee|Ranged|Tactic|Space|Reach|Special|Ranged|Stat|Spell)")]
[H: MeleeSearch = strfind(statblock, "(?i)(Melee (.+?))(Tactic|Space|Reach|Special|Ranged|Stat|Spell)")]
[H: RangedSearch = strfind(statblock, "(?i)(Ranged (.+?))(Tactic|Space|Reach|Special|Stat|Spell)")]
[H: SpaceReachSearch = strfind(statblock, "(?i)((Space|Reach)(.*?))(Tactic|Special|Stat|Spell)")]
[H: SpecialAttackSearch = strfind(statblock, "(?i)(Special Attacks?(.*?))(Tactic|Stat|Spell)")]
[H: SpellLikeSearch = strfind(statblock,  "(?i)((Arcane School |Gnome |Domain |Bloodline |Hellknight |Transmuter |Paladin |Cleric |Conjurer |Enchanter |Evoker |Necromancer |Arcane )?Spell-Like Abilities (.*?))(?=(Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Arcane School|Gnome|Domain|Bloodline|Hellknight|Arcane|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter|Tactic|Face|Stat|Spell))")]
[H: SpellsKnownSearch = strfind(statblock, "(?i)((Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter)? ?Spells Known(.*?))(?=(Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter|Tactic|Stat|Spells))")]
[H: SpellsPreparedSearch = strfind(statblock, "(?i)((Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter)? ?Spells Prepared(.*?))(?=(Alchemist|Antipaladin|Bard|Cleric|Oracle|Druid|Inquisitor|Magus|Paladin|Ranger|Sorcerer|Wizard|Summoner|Witch|Abjurer|Conjurer|Diviner|Enchanter|Evoker|Illusionist|Necromancer|Transmuter|Tactic|Stat|Spells))")]
[H: TacticsSearch = strfind(statblock, "(?i)TACTICS(.*?)(?<!base )(?:STATISTICS)")]
[H: StatisticsSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Str (.*?))Base Atk")]
[H: BaseAtkSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Base Atk (.*?))(Feats|Skills|Languages|SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: FeatsSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Feats (.*?))(Skills|Languages|SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: SkillsSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Skills (.*?))(Languages|SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: LanguagesSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(Languages? (.*?))(SQ|Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: SQSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?(SQ (.*?))(Combat Gear|Other Gear|Gear|SPECIAL|ECOLOGY)")]
[H: GearSearch = strfind(statblock, "(?i)(base statistics)?.*?Statistics.*?((Combat )?Gear (.*?))(BASE STATISTICS|SPECIAL|ECOLOGY) ")]
[H: BaseStatisticsSearch = strfind(statblock, "(?i)(Base Statistics.*?)(STATISTICS.*?Str [0-9]|SPECIAL ABILITIES|ECOLOGY)")]
[H: SpecialAbilitiesSearch = strfind(statblock, "(?i)(SPECIAL ABILITIES)(.*?)(ECOLOGY)")]

<!-- If the line or section exists, put it in a variable and give it a line break before it -->
[H, IF(0<getFindCount(GenderEtcSearch)), CODE: {
	[GenderEtcNote = "
" + trim(getGroup(GenderEtcSearch, 1, 1))]
};{
	[GenderEtcNote = ""]
}]

[H, IF(0<getFindCount(AuraSearch)), CODE: {
	[AuraNote = "
" + trim(getGroup(AuraSearch, 1, 1))]
};{
	[AuraNote = ""]
}]

[H, IF(0<getFindCount(DefensiveAbilitiesSearch)), CODE: {
	[DANote = "
" + trim(getGroup(DefensiveAbilitiesSearch, 1, 1))]
};{
	[DANote = ""]
}]

[H, IF(0<getFindCount(WeaknessesSearch)), CODE: {
	[WeaknessNote = "
" + trim(getGroup(WeaknessesSearch, 1, 1))]
};{
	[WeaknessNote = ""]
}]

[H, IF(0<getFindCount(MeleeSearch)), CODE: {
	[MeleeNote = "
" + trim(getGroup(MeleeSearch, 1, 1))]
};{
	[MeleeNote = ""]
}]

[H, IF(0<getFindCount(RangedSearch)), CODE: {
	[RangedNote = "
" + trim(getGroup(RangedSearch, 1, 1))]
};{
	[RangedNote = ""]
}]

[H, IF(0<getFindCount(SpaceReachSearch)), CODE: {
	[SpaceReachNote = "
" + trim(getGroup(SpaceReachSearch, 1, 1))]
};{
	[SpaceReachNote = ""]
}]

[H, IF(0<getFindCount(SpecialAttackSearch)), CODE: {
	[SANote = "
" + trim(getGroup(SpecialAttackSearch, 1, 1))]
};{
	[SANote = ""]
}]

<!-- If a Spell-Like Abilities section exists, break it up into subsections based on constant/at will/or # per day. Multiple Spell-Like sections may exist from different sources, so create a section for each -->
[H: SLNote = ""]
[H, IF(0<getFindCount(SpellLikeSearch)), FOR(NumSpellLikeTypes, 1, getFindCount(SpellLikeSearch)+1), CODE: {
	[SpellLikeString = getGroup(SpellLikeSearch, NumSpellLikeTypes, 1)]
	[SpellLikeHeaderSearch = strfind(SpellLikeString, "(?i)(.*?)(Constant|At[ -]Will|[0-9]+/day)")]
	[ConstantSearch = strfind(SpellLikeString, "(?i)((Constant)(.*?))(At[ -]Will|[0-9]+/day)")]
	[IF(1>getFindCount(ConstantSearch)): ConstantSearch = strfind(SpellLikeString, "(?i)((Constant)(.*))")]
	[AtWillSearch = strfind(SpellLikeString, "(?i)((At[ -]Will)(.*?))([0-9]+/day)")]
	[IF(1>getFindCount(AtWillSearch)): AtWillSearch = strfind(SpellLikeString, "(?i)((At[ -]Will)(.*))")]
	[PerDayStringSearch = strfind(SpellLikeString, "(?i)(([0-9]+/day)(.*))")]
	[IF(0<getFindCount(PerDayStringSearch)): PerDayString = getGroup(PerDayStringSearch, 1, 1); PerDayString = ""]
	[PerDayTypesSearch = strfind(PerDayString, "(?i)(([0-9]+/day)(.*?))(?=([0-9]+/day))")]
	[PerDayAbilities = ""]
	[IF(0<getFindCount(PerDayTypesSearch)), FOR(DifferentPerDays, 1, getFindCount(PerDayTypesSearch)+1), CODE: {
		[PerDayString = replace(PerDayString, "(?i)([0-9]+/day(.*?))(?=([0-9]+/day))", "")]
		[ThisPerDay = trim(getGroup(PerDayTypesSearch, DifferentPerDays, 1))]
		[PerDayAbilities = PerDayAbilities + "
	" + ThisPerDay]
	}]
	[IF(PerDayString != ""): PerDayAbilities = PerDayAbilities + "
	" + trim(PerDayString)]
	[IF(0<getFindCount(SpellLikeHeaderSearch)): SpellLikeHeader = "

" + trim(getGroup(SpellLikeHeaderSearch, 1, 1)); SpellLikeHeader = "" ]
	[IF(0<getFindCount(ConstantSearch)), CODE: {
		[ConstantAbilities = "
	" + trim(getGroup(ConstantSearch, 1, 1))]
	};{
		[ConstantAbilities = ""]
	}]
	[IF(0<getFindCount(AtWillSearch)), CODE: {
		[AtWillAbilities = "
	" + trim(getGroup(AtWillSearch, 1, 1))]
	};{
		[AtWillAbilities = ""]
	}]
	[SLNote = SLNote + SpellLikeHeader + ConstantAbilities + AtWillAbilities + PerDayAbilities]
};{
	[SLNote = ""]
}]

<!-- If a Spells Known section exists, break it up into subsections based on spell level. Multiple Spells Known sections may exist from different sources, so create a section for each -->
[SKNote = ""]
[H, IF(0<getFindCount(SpellsKnownSearch)), FOR(NumSpellClasses, 1, getFindCount(SpellsKnownSearch)+1), CODE: {
	[ThisSpellClass = getGroup(SpellsKnownSearch, NumSpellClasses, 1)]
	[SpellsKnownHeaderSearch = strfind(ThisSpellClass, "(?i)(.*?\\)) +(9th|8th|7th|6th|5th|4th|3rd|2nd|1st|0 \\(|0th \\()")]
	[NinthLevelSearch = strfind(ThisSpellClass, "(?i)((9th)(.*?))(8th)")]
	[EighthLevelSearch = strfind(ThisSpellClass, "(?i)((8th)(.*?))(7th)")]
	[SeventhLevelSearch = strfind(ThisSpellClass, "(?i)((7th)(.*?))(6th)")]
	[SixthLevelSearch = strfind(ThisSpellClass, "(?i)((6th)(.*?))(5th)")]
	[FifthLevelSearch = strfind(ThisSpellClass, "(?i)((5th)(.*?))(4th)")]
	[FourthLevelSearch = strfind(ThisSpellClass, "(?i)((4th)(.*?))(3rd)")]
	[ThirdLevelSearch = strfind(ThisSpellClass, "(?i)((3rd)(.*?))(2nd)")]
	[SecondLevelSearch = strfind(ThisSpellClass, "(?i)((2nd)(.*?))(1st)")]
	[FirstLevelSearch = strfind(ThisSpellClass, "(?i)((1st)(.*?))(0 \\(|0th \\()")]
	[ThisClassSpecialSearch = strfind(ThisSpellClass, "(?i)((D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)(.*))")]
	[IF(0<getFindCount(ThisClassSpecialSearch)): ZerothLevelSearch = strfind(ThisSpellClass, "(?i)((0 \\(|0th \\()(.*?))(D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)"); ZerothLevelSearch = strfind(ThisSpellClass, "((0 \\(|0th \\()(.*))")]
	[IF(0<getFindCount(SpellsKnownHeaderSearch)): SpellsKnownHeader = "

" + trim(getGroup(SpellsKnownHeaderSearch, 1, 1)); SpellsKnownHeader = ""]
	[IF(0<getFindCount(NinthLevelSearch)), CODE: {
		[NinthLevel = "
	" + trim(getGroup(NinthLevelSearch, 1, 1))]
	};{
		[NinthLevel = ""]
	}]
	[IF(0<getFindCount(EighthLevelSearch)), CODE: {
		[EighthLevel = "
	" + trim(getGroup(EighthLevelSearch, 1, 1))]
	};{
		[EighthLevel = ""]
	}]
	[IF(0<getFindCount(SeventhLevelSearch)), CODE: {
		[SeventhLevel = "
	" + trim(getGroup(SeventhLevelSearch, 1, 1))]
	};{
		[SeventhLevel = ""]
	}]
	[IF(0<getFindCount(SixthLevelSearch)), CODE: {
		[SixthLevel = "
	" + trim(getGroup(SixthLevelSearch, 1, 1))]
	};{
		[SixthLevel = ""]
	}]
	[IF(0<getFindCount(FifthLevelSearch)), CODE: {
		[FifthLevel = "
	" + trim(getGroup(FifthLevelSearch, 1, 1))]
	};{
		[FifthLevel = ""]
	}]
	[IF(0<getFindCount(FourthLevelSearch)), CODE: {
		[FourthLevel = "
	" + trim(getGroup(FourthLevelSearch, 1, 1))]
	};{
		[FourthLevel = ""]
	}]
	[IF(0<getFindCount(ThirdLevelSearch)), CODE: {
		[ThirdLevel = "
	" + trim(getGroup(ThirdLevelSearch, 1, 1))]
	};{
		[ThirdLevel = ""]
	}]
	[IF(0<getFindCount(SecondLevelSearch)), CODE: {
		[SecondLevel = "
	" + trim(getGroup(SecondLevelSearch, 1, 1))]
	};{
		[SecondLevel = ""]
	}]
	[IF(0<getFindCount(FirstLevelSearch)), CODE: {
		[FirstLevel = "
	" + trim(getGroup(FirstLevelSearch, 1, 1))]
	};{
		[FirstLevel = ""]
	}]
	[IF(0<getFindCount(ZerothLevelSearch)), CODE: {
		[ZerothLevel = "
	" + trim(getGroup(ZerothLevelSearch, 1, 1))]
	};{
		[ZerothLevel = ""]
	}]
	[IF(0<getFindCount(ThisClassSpecialSearch)), CODE: {
		[ThisClassSpecial = "
	" + trim(getGroup(ThisClassSpecialSearch, 1, 1))]
	};{
		[ThisClassSpecial = ""]
	}]
	[SKNote = SKNote + SpellsKnownHeader + NinthLevel + EighthLevel + SeventhLevel + SixthLevel + FifthLevel + FourthLevel + ThirdLevel + SecondLevel + FirstLevel + ZerothLevel + ThisClassSpecial]
};{
	[SKNote = ""]
}]

<!-- If a Spells Prepared section exists, break it up into subsections based on spell level. Multiple Spells Prepared sections may exist from different sources, so create a section for each -->
[H: SPNote = ""]
[H, IF(0<getFindCount(SpellsPreparedSearch)), FOR(NumSpellClasses, 1, getFindCount(SpellsPreparedSearch)+1), CODE: {
	[ThisSpellClass = getGroup(SpellsPreparedSearch, NumSpellClasses, 1)]
	[SpellsPreparedHeaderSearch = strfind(ThisSpellClass, "(?i)(.*?\\)) +(9th|8th|7th|6th|5th|4th|3rd|2nd|1st|0 \\(|0th \\))")]
	[NinthLevelSearch = strfind(ThisSpellClass, "(?i)((9th)(.*?))(8th)")]
	[EighthLevelSearch = strfind(ThisSpellClass, "(?i)((8th)(.*?))(7th)")]
	[SeventhLevelSearch = strfind(ThisSpellClass, "(?i)((7th)(.*?))(6th)")]
	[SixthLevelSearch = strfind(ThisSpellClass, "(?i)((6th)(.*?))(5th)")]
	[FifthLevelSearch = strfind(ThisSpellClass, "(?i)((5th)(.*?))(4th)")]
	[FourthLevelSearch = strfind(ThisSpellClass, "(?i)((4th)(.*?))(3rd)")]
	[ThirdLevelSearch = strfind(ThisSpellClass, "(?i)((3rd)(.*?))(2nd)")]
	[SecondLevelSearch = strfind(ThisSpellClass, "(?i)((2nd)(.*?))(1st)")]
	[FirstLevelSearch = strfind(ThisSpellClass, "(?i)((1st)(.*?))(0 \\(|0th \\()")]
	[ThisClassSpecialSearch = strfind(ThisSpellClass, "(?i)((D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)(.*))")]
	[IF(0<getFindCount(ThisClassSpecialSearch)): ZerothLevelSearch = strfind(ThisSpellClass, "(?i)((0 \\(|0th \\()(.*?))(D Domain|Bloodline|Domain|Mystery|Opposition School|Prohibited School)"); ZerothLevelSearch = strfind(ThisSpellClass, "((0 \\(|0th \\()(.*))")]
	[IF(0<getFindCount(SpellsPreparedHeaderSearch)): SpellsPreparedHeader = "

" + trim(getGroup(SpellsPreparedHeaderSearch, 1, 1)); SpellsPreparedHeader = ""]
	[IF(0<getFindCount(NinthLevelSearch)), CODE: {
		[NinthLevel = "
	" + trim(getGroup(NinthLevelSearch, 1, 1))]
	};{
		[NinthLevel = ""]
	}]
	[IF(0<getFindCount(EighthLevelSearch)), CODE: {
		[EighthLevel = "
	" + trim(getGroup(EighthLevelSearch, 1, 1))]
	};{
		[EighthLevel = ""]
	}]
	[IF(0<getFindCount(SeventhLevelSearch)), CODE: {
		[SeventhLevel = "
	" + trim(getGroup(SeventhLevelSearch, 1, 1))]
	};{
		[SeventhLevel = ""]
	}]
	[IF(0<getFindCount(SixthLevelSearch)), CODE: {
		[SixthLevel = "
	" + trim(getGroup(SixthLevelSearch, 1, 1))]
	};{
		[SixthLevel = ""]
	}]
	[IF(0<getFindCount(FifthLevelSearch)), CODE: {
		[FifthLevel = "
	" + trim(getGroup(FifthLevelSearch, 1, 1))]
	};{
		[FifthLevel = ""]
	}]
	[IF(0<getFindCount(FourthLevelSearch)), CODE: {
		[FourthLevel = "
	" + trim(getGroup(FourthLevelSearch, 1, 1))]
	};{
		[FourthLevel = ""]
	}]
	[IF(0<getFindCount(ThirdLevelSearch)), CODE: {
		[ThirdLevel = "
	" + trim(getGroup(ThirdLevelSearch, 1, 1))]
	};{
		[ThirdLevel = ""]
	}]
	[IF(0<getFindCount(SecondLevelSearch)), CODE: {
		[SecondLevel = "
	" + trim(getGroup(SecondLevelSearch, 1, 1))]
	};{
		[SecondLevel = ""]
	}]
	[IF(0<getFindCount(FirstLevelSearch)), CODE: {
		[FirstLevel = "
	" + trim(getGroup(FirstLevelSearch, 1, 1))]
	};{
		[FirstLevel = ""]
	}]
	[IF(0<getFindCount(ZerothLevelSearch)), CODE: {
		[ZerothLevel = "
	" + trim(getGroup(ZerothLevelSearch, 1, 1))]
	};{
		[ZerothLevel = ""]
	}]
	[IF(0<getFindCount(ThisClassSpecialSearch)), CODE: {
		[ThisClassSpecial = "
	" + trim(getGroup(ThisClassSpecialSearch, 1, 1))]
	};{
		[ThisClassSpecial = ""]
	}]
	[SPNote = SPNote + SpellsPreparedHeader + NinthLevel + EighthLevel + SeventhLevel + SixthLevel + FifthLevel + FourthLevel + ThirdLevel + SecondLevel + FirstLevel + ZerothLevel + ThisClassSpecial]
};{
	[SPNote = ""]
}]

<!-- If a tactics section exists, break it up into subsections of Before Combat, During Combat, and Morale. The Base Statistics section, if it exists, may be in Tactics or it may be at the end of the Statistics section. In either case, put it in Tactics -->
[H, IF(0<getFindCount(TacticsSearch)), CODE: {
	[TacticsNote = "

-----TACTICS-----"]
	[TacticsString = getGroup(TacticsSearch, 1, 1)]
	[TacticsBeforeCombatSearch = strfind(TacticsString, "(?i)(Before Combat.*?)(During Combat|Morale|Base Statistics)")]
	[IF(1>getFindCount(TacticsBeforeCombatSearch)): TacticsBeforeCombatSearch = strfind(TacticsString, "(?i)(Before Combat.*)")]
	[IF(0<getFindCount(TacticsBeforeCombatSearch)): TacticsNote = TacticsNote + "
" + trim(getGroup(TacticsBeforeCombatSearch, 1, 1))]
	[TacticsDuringCombatSearch = strfind(TacticsString, "(?i)(During Combat.*?)(Morale|Base Statistics)")]
	[IF(1>getFindCount(TacticsDuringCombatSearch)): TacticsDuringCombatSearch = strfind(TacticsString, "(?i)(During Combat.*)")]
	[IF(0<getFindCount(TacticsDuringCombatSearch)): TacticsNote = TacticsNote + "
" + trim(getGroup(TacticsDuringCombatSearch, 1, 1))]
	[TacticsMoraleSearch = strfind(TacticsString, "(?i)(Morale.*?)(Base Statistics)")]
	[IF(1>getFindCount(TacticsMoraleSearch)): TacticsMoraleSearch = strfind(TacticsString, "(?i)(Morale.*)")]
	[IF(0<getFindCount(TacticsMoraleSearch)): TacticsNote = TacticsNote + "
" + trim(getGroup(TacticsMoraleSearch, 1, 1))]
};{
	[TacticsNote = ""]
}]
[H, IF(0<getFindCount(BaseStatisticsSearch)), CODE: {
	[IF(TacticsNote == ""), CODE: {
		[TacticsNote = "

-----TACTICS-----
" + trim(getGroup(BaseStatisticsSearch, 1, 1)))]
	};{
		[TacticsNote = TacticsNote + "
" + trim(getGroup(BaseStatisticsSearch, 1, 1)))]
	}]
}]

[H, IF(0<getFindCount(FeatsSearch)), CODE: {
	[FeatsNote = "
" + trim(getGroup(FeatsSearch, 1, 2))]
};{
	[FeatsNote = ""]
}]

[H, IF(0<getFindCount(SkillsSearch)), CODE: {
	[SkillsNote = "
" + trim(getGroup(SkillsSearch, 1, 2))]
};{
	[SkillsNote = ""]
}]

[H, IF(0<getFindCount(LanguagesSearch)), CODE: {
	[LangNote = "
" + trim(getGroup(LanguagesSearch, 1, 2))]
};{
	[LangNote = ""]
}]

[H, IF(0<getFindCount(SQSearch)), CODE: {
	[SQNote = "
" + trim(getGroup(SQSearch, 1, 2))]
};{
	[SQNote = ""]
}]

[H, IF(0<getFindCount(GearSearch)), CODE: {
	[GearNote = "
" + trim(getGroup(GearSearch, 1, 2))]
};{
	[GearNote = ""]
}]

<!-- If Special Abilities section exists, break it up into a separate line for each ability with its description -->
[H, IF(0<getFindCount(SpecialAbilitiesSearch)), CODE: {
	[SAbNote = "

-----SPECIAL ABILITIES-----"]
	[SAList = ""]
	[SpecialAbilityString = getGroup(SpecialAbilitiesSearch, 1, 2)]
	[SingleSpecialAbilitySearch = strfind(SpecialAbilityString, "(?i)(.*?\\. +)(?=.*? \\((Ex|Su|Sp)\\))")]
	[IF(0<getFindCount(SingleSpecialAbilitySearch)), FOR(NumOfSpecialAbilities, 1, getFindCount(SingleSpecialAbilitySearch)+1), CODE: {
		[SingleSpecialAbility = getGroup(SingleSpecialAbilitySearch, NumOfSpecialAbilities, 1)]
		[SpecialAbilityString = replace(SpecialAbilityString, "(?i)(.*?\\. +)(?=.*? \\((Ex|Su|Sp)\\))", "")]
		[SearchingForExtraLines = strfind(SingleSpecialAbility, "(?i)\\((Ex|Su|Sp)\\)")]
		[IF(0<getFindCount(SearchingForExtraLines)): SAList = SAList + "
" + trim(SingleSpecialAbility) ; SAList = SAList + " " + trim(SingleSpecialAbility)]
	}]
	[SAList = SAList + "
" + trim(SpecialAbilityString)]
	[SAbNote = SAbNote + SAList]
};{
	[SAbNote = ""]
}]

<!-- If the Ecology section exists, break it up into lines for Environment, Organization, and Treasure -->
[H, IF(0<getFindCount(EcologySearch)), CODE: {
	[EcologyNote = "

-----ECOLOGY-----"]
	[EcologyString = trim(getGroup(EcologySearch, 1, 2))]
	[EnvironmentSearch = strfind(EcologyString, "(?i)(Environment.*?)(Organization|Treasure)")]
	[IF(0<getFindCount(EnvironmentSearch)): EcologyNote = EcologyNote + "
" + trim(getGroup(EnvironmentSearch, 1, 1))]
	[OrganizationSearch = strfind(EcologyString, "(?i)(Organization.*?)(Treasure)")]
	[IF(0<getFindCount(OrganizationSearch)): EcologyNote = EcologyNote + "
" + trim(getGroup(OrganizationSearch, 1, 1))]
	[TreasureSearch = strfind(EcologyString, "(?i)(Treasure.*)")]
	[IF(0<getFindCount(TreasureSearch)), CODE: {
		[TreasureTypeSearch = strfind(getGroup(TreasureSearch, 1, 1), "(none|standard|double|incidental|NPC gear)")]
		[IF(0<getFindCount(TreasureTypeSearch)): TreasureType = getGroup(TreasureTypeSearch, 1, 1)]
	}]
	[IF(0<getFindCount(TreasureSearch)): EcologyNote = EcologyNote + "
" + trim(getGroup(TreasureSearch, 1, 1))]
};{
	[EcologyNote = ""]
}]

<!--Put it all together in the GM notes -->
[H: setGMNotes(trim(getGroup(NameSearch, 1, 1)) + "	" + trim(getGroup(CRSearch, 1, 1)) + "
------------
" + trim(getGroup(XPSearch, 1, 1)) +
 GenderEtcNote + "
" + trim(getGroup(AlignmentSizeRaceSearch, 1, 1)) + "
" + trim(getGroup(InitSensesSearch, 1, 1)) + 
 AuraNote + "

-----DEFENSE-----
" + trim(getGroup(ACStringSearch, 1, 1)) + "
" + trim(getGroup(HPStringSearch, 1, 1)) + "
" + trim(getGroup(SavesSearch, 1, 1)) + 
 DANote + 
 WeaknessNote + "

-----OFFENSE-----
" + trim(getGroup(SpeedSearch, 1, 1)) +
 MeleeNote +
 RangedNote +
 SpaceReachNote +
 SANote +
 SLNote +
 SKNote +
 SPNote +
 TacticsNote + "

-----STATISTICS-----
" + trim(getGroup(StatisticsSearch, 1, 2)) + "
" + trim(getGroup(BaseAtkSearch, 1, 2)) + 
 FeatsNote +
 SkillsNote +
 LangNote +
 SQNote +
 GearNote +
 SAbNote +
 EcologyNote)]

[H: output="Token Updated:<br>"]
[H: id = strfind(statblock, "^(.+?)CR ?[0-9]")]
[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 ?)([0-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(0)]
	[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+", "]
}]

 <!-- Defensive Abilities Sample: Defensive Abilities incorporeal, channel resistance +2; -->
[H: DefensiveAbil = ""]
[H, IF(0<getFindCount(DefensiveAbilitiesSearch)),CODE: {
	[DefensiveAbilitiesString=getGroup(DefensiveAbilitiesSearch, 1, 1)]
	[DefensiveAloneSearch=strfind(DefensiveAbilitiesString, "(?i)Defensive (Abilities|Ability)(.*?); +(SR|Immune|Resist|DR)")]
	[IF(0==getFindCount(DefensiveAloneSearch)): DefensiveAloneSearch = strfind(DefensiveAbilitiesString, "(?i)Defensive (Ability|Abilities)(.*)")]
	[IF(0<getFindCount(DefensiveAloneSearch)), CODE: {
		[DefensiveAbil = trim(getGroup(DefensiveAloneSearch, 1, 2))]
		[output=output+"Defensive Abilities: "+DefensiveAbil+", "]
	}]
}]

<!-- Find Immunities. Sample: Immune acid, curse effects, flanking, mind-affecting effects, paralysis, poison, sleep -->
[H: Immune = ""]
[H: id = strfind(statblock, "(Immune )(([a-zA-Z]+[-]?,? ?)+);? +(Weakness|OFFENSE|Resist|SR)")]
[H, IF(0<getFindCount(id)),CODE: {
	[id2 = strfind(getGroup(id, 1, 2), "(.*) (Weakness|OFFENSE|[;]|Resist)")]
	[IF(0<getFindCount(id2)): Immune = getGroup(id2, 1, 1); Immune = getGroup(id, 1, 2)]
	[Immune = trim(Immune)]
	[output=output+"Immunities: "+Immune+", "]
 }]

<!-- Find Weaknesses. Sample: Weaknesses light sensitivity -->
[H: Weaknesses = ""]
[H: id = strfind(statblock, "(Weaknesse?s? )((\\w+-?,? ?)+) +(?i)(OFFENSE|DEFENSE)")]
[H, IF(0<getFindCount(id)),CODE: {
	[Weaknesses = trim(getGroup(id, 1, 2))]
	[output=output+"Weaknesses: "+Weaknesses+", "]
}]
  
<!-- Find DR. Sample: 1/-, 2/adamantine, 3/good -->
[h: DRSearch = ""]
[H: id = strfind(statblock, "DR(.?[0-9]+)/(.+?)[,;O]")]
[H, IF(0<getFindCount(id)), COUNT(getFindCount(id)), CODE: {
	[thisrollcount = roll.count+1]
	[thissearchDR = replace(getGroup(id, thisrollcount, 2), "Cold Iron", "ColdIron")]
	[drANDORsearch = strfind(thissearchDR, "(and|or) ([a-zA-Z]+ ?)")]
	[if(0<getFindCount(drANDORsearch)), COUNT(getFindCount(drANDORsearch)), CODE: {
	[DRSearch = trim(DRSearch + " " + getGroup(id, thisrollcount, 1)+"/"+getGroup(drANDORsearch, roll.count+1, 2))]
	}]
	[DRSearch = trim(DRSearch + " " + getGroup(id, thisrollcount, 1)+"/"+thissearchDR)]
	[DRSearch = replace(DRSearch, "/-", "/All")]
	[DRSearch = replace(DRSearch, "(?i)/Cold Iron", "/ColdIron")]
	[DRSearch = replace(DRSearch, "(?i)/Chaotic", "/Chaos")]
	[DRSearch = replace(DRSearch, "(?i)/Lawful", "/Law")]
}]
  
<!-- Find Resistences. Sample: Resist acid 5, cold 5, electricity 5 -->
[h: resistSearch = ""]
[H: id = strfind(statblock, "(\\bResist \\b(\\w+ \\d+,? ?)+)")]
[H, IF(0<getFindCount(id)),CODE: {
	[resistSearch = trim(getGroup(id, 1, 1))]
}]

<!-- Find SR -->  
[H: SRSearch = ""]
[H: id = strfind(statblock, "SR ([0-9]+)")]
[H, IF(0<getFindCount(id)),CODE: {
	[SRSearch=getGroup(id, 1, 1)]
}]

<!-- Put DR & Resist values into JSON Property DRER -->
[H, FOREACH( reduceTypes, json.fields( DRER ) ), CODE: {
 	[jRC = json.get( DRER, reduceTypes ) ]
 	[prefixT = substring( reduceTypes, 0, 1 ) ]
   

 	[FOREACH( reduceType, json.fields( jRC ) ), CODE: {
    	[preValue = json.get( jRC, reduceType ) ]
		[reduceVal = 0]

		[resistFind = strfind(resistSearch, "(?i)("+reduceType+" )(\\d+)")]

		 [IF(0<getFindCount(resistFind) && prefixT == "E"): reduceVal = getGroup(resistFind, 1, 2)]

		[drFind = strfind(DRSearch, "(?i)(\\d+)/("+reduceType+")")]
		[IF(0<getFindCount(drFind) && prefixT == "D"): reduceVal = getGroup(drFind, 1, 1)]
  
		[ chgValue = eval( "" + prefixT + reduceType + "=" + reduceVal ) ]
	  
    	[IF( reduceType != "Note" ): tmodV = json.get( DRERMod, if( reduceTypes == "Damage Reduction", "DR_", if( reduceTypes == "Energy Resistance", "ER_", if( reduceTypes == "Spell Resistance", "SR_", "EV_") ) ) + reduceType ) ]
    	[IF( reduceType != "Note" ): tVal = max( chgValue, tmodV ); tVal = chgValue ]
     
		[IF( tVal != 0 && tVal != "" && prefixT == "D"): DR = listAppend( DR, tVal + if( reduceType == "Note", "", "/" + if( reduceType == "All", "-", reduceType ) ) ) ]
	  
		[IF( tVal != 0 && tVal != "" && prefixT == "E"): DR = listAppend( DR, reduceType + if( reduceType == "Note", "", " (" + if( tVal == "999", "Immune", tVal ) + ")" ) ) ]
    	[IF( reduceType == "Note" && chgValue == 0 ): chgValue = "" ]
    	[IF( preValue != chgValue ):  output = output + reduceType + "= " + chgValue + " (" + preValue + "), "]
    	[IF( preValue != chgValue ): jRC = json.set( jRC, reduceType, chgValue ) ]

}]
 	[setProperty( "DRER", json.set( DRER, reduceTypes, jRC ) ) ]
   
 	[IF(DRSearch != "" || resistSearch != ""): output=output+"<br>"]
}]

[H, If(Immune != ""), CODE:{
	[jRC = json.get( DRER, "Energy Resistance" )]
	[jRCnew = json.set( jRC, "Note", "Immune: "+Immune)]
	[newDRER = json.set( DRER, "Energy Resistance", JRCnew)]
	[setProperty("DRER", newDRER)]
	[DR = listAppend(DR, "Immune: "+Immune)]
}]

[H, If(SRSearch != ""), CODE:{
	[jRC = json.get( DRER, "Spell Resistance" )]
	[jRCnew = json.set( jRC, "SR", SRSearch)]
	[newDRER = json.set( DRER, "Spell Resistance", JRCnew)]
	[setProperty("DRER", newDRER)]
	[DR = listAppend(DR, "SR: "+SRSearch)]
}]

[H: vulnerability =  strfind(Weaknesses, "(Vulnerability|Vulnerable|vulnerability|vulnerable) to ([a-zA-Z]*)")]
[H: vtypes = getFindCount(vulnerability)]
[H, IF(0<vtypes), CODE: {
	[COUNT(vtypes), CODE: {
		[jRC = json.get(DRER, "Energy Vulnerability")]
      [ tvuln = "V."+upper(lower(getGroup(vulnerability,roll.count+1,2)),1)]
      [ validVulnerability = listContains("V.Acid,V.Cold,V.Electricity,V.Fire,V.Sonic",tvuln)]
		[if(validVulnerability): jRCnew = json.set(jRC, tvuln, 1)]
		[if(validVulnerability): newDRER = json.set(DRER, "Energy Vulnerability", jRCnew)]
		[if(validVulnerability): setProperty("DRER", newDRER)]
		[if(validVulnerability): DR = listAppend(DR, tvuln)]
	}]
}]

<!-- STATS BEGIN -->
[H: id = strfind(statblock, "(?i)(?<!base )STATISTICS.*?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>"]
}]
[H: SavesString = getGroup(SavesSearch, 1, 1)]
[H: id = strfind(SavesString, ";(.*)")]
[H, IF(0<getFindCount(id)), CODE: {
	[SaveMisc = trim(getGroup(id, 1, 1))]
	[output=output+"Saves Misc Notes: "+SaveMisc+"<br>"]
}]

<!-- SAVES END -->

[H: id = strfind(statblock, "hp ([0-9]+).*?\\(([0-9]+ ?HD; ?)?([0-9d+]*)")]
[H: hpString = getGroup(id, 1, 3)]
[H: numOfEachHDType = strfind(hpString, "([0-9]+)d")]
[H: numOfHDTypes = getFindCount(numOfEachHDType)]
[H, IF(0<getFindCount(id)),CODE: {
	[H: HD = 0]
	[H, COUNT(numOfHDTypes), CODE:
	{
		[HD = HD + getGroup(numOfEachHDType, (roll.count+1), 1)]
	}]
	[HP=getGroup(id, 1, 1)]
	[Level = max(1,HD)]
	[HPmax="[R: max(Level, ( "+(HP-(ConB*Level))+" + (ConB * Level) ) )]"]
	[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-natural]
	[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)]
	[Movement = spd]
}; {
	[Speed = json.set(Speed, "base",0)]
	[Movement = 0]
}]
[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)]
	[Movement = getProperty("Movement") + ", Climb:" + 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)]
	[Movement = getProperty("Movement") + ", 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)]
	[Movement = getProperty("Movement") + ", Burrow:" + spd]
}]
[H: id = strfind(statblock, "fly ([0-9]+) ft\\.? ?\\(?([a-zA-Z]+)?\\)? ?(Melee|Ranged|Special|Spell)")]
[H, IF(0<getFindCount(id)), CODE: {
	[spd=getGroup(id, 1, 1)]
	[output=output+", Fly: "+spd]
	[Speed = json.set(Speed, "fly",spd)]
	[flyman=lower(getGroup(id, 1, 2))]
	[SWITCH(flyman), CODE:
		case "": {
			[flyman = "average"]
			[Speed = json.set(Speed, "flymaneuver",4)]
		};
		case "clumsy": {
			[flyman = "Clumsy"]
			[Speed = json.set(Speed, "flymaneuver",2)]
		};
		case "poor": {
			[flyman = "Poor"]
			[Speed = json.set(Speed, "flymaneuver",3)]
		};
		case "average": {
			[flyman = "Average"]
			[Speed = json.set(Speed, "flymaneuver",4)]
		};
		case "good": {
			[flyman = "Good"]
			[Speed = json.set(Speed, "flymaneuver",5)]
		};
		case "perfect": {
			[flyman = "Perfect"]
			[Speed = json.set(Speed, "flymaneuver",6)]
		};
	]
	[Movement = getProperty("Movement") + ", Fly:" + spd + " (" + flyman + ")"]
}]
[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 -->

<!--Languages Begin-->
[H, IF(0<getFindCount(LanguagesSearch)), CODE: {
	[LanguageList = strfind(getGroup(LanguagesSearch, 1, 2), "Languages?(.*)")]
	[IF(0<getFindCount(LanguageList)): Race = json.set(Race, "notes", trim(getGroup(LanguageList, 1, 1)))]
}]
<!--Languages End-->

<!-- SQ on popup begin -->
[H, IF(0<getFindCount(SQSearch)), CODE: {
	[SpecialQualWithoutSQ = strfind(getGroup(SQSearch, 1, 2), "SQ (.*)")]
	[SpecialQual = trim(getGroup(SpecialQualWithoutSQ, 1, 1))]
}]
<!--SQ on popup end-->

<!-- FEATS BEGIN -->
[H: output=output+"Feats: "]
[H, IF(0<getFindCount(FeatsSearch)), CODE: {
   [ tFeats = replace(getGroup(FeatsSearch, 1, 2), "Feats ", "", 1) ]
   [ tFeats = trim(replace(tFeats, "B,", ",")) ]
   [ tFEflag = endsWith(tFeats, "B") ]
   [ IF(tFEflag): tFeats = substring(tFeats, 0, length(tFeats)-1); "" ]
   [ tFeatsJ = json.fromList(tFeats) ]
   [ tFeatsJ2 = "[]" ]
   [ tFeatsJparam = "{}" ]
   [ Foreach(p, tFeatsJ), CODE: {
      [ tidxp = indexOf(p,"(") ]
      [ IF( tidxp < 0 ): tpm = trim(p); tpm = trim(substring(p,0,tidxp)) ]
      [ IF( tidxp < 0 ): tpsub = ""; tpsub = substring(p,tidxp+1,length(p)-1) ]
      [ tFeatsJ2 = json.append(tFeatsJ2, tpm) ]
      [ IF( tidxp < 0 ): "";  tFeatsJparam = json.set(tFeatsJparam, tpm, tpsub) ]
   }]
   [ tFeatsJ2 = json.sort(tFeatsJ2) ]
   [ setProperty("FeatsJ", tFeatsJ2) ]
   [ setProperty("FeatsJparam", tFeatsJparam) ]
   
   [H: sysVars = table("SysVars", 0)]
   [H: sysFeats = table("SysVars", json.get(sysVars, "sysFeats"))]
   [H: tsfl = json.fields(sysFeats,"json")]
   [H: tFeatsSys = getProperty("Feats")]
   [H, FOREACH(p,tsfl),CODE: {
      [H: tfcont = json.contains(tFeatsJ2,p)]
      [H: tval = if(tfcont,1,0)]
      [H: tFeatsSys = setStrProp(tFeatsSys,json.get(sysFeats,p),tval)]
   }]
   [H: setProperty("Feats", tFeatsSys)]
   [H: output=output+tFeats ]
};{
}]
[H: output=output+"<br>"]
<!-- FEATS END -->

<!-- Ferocity for Orcs works just like Die Hard -->
[H: id = strfind(DefensiveAbil, "(?i)(ferocity)")]
[H, IF(0<getFindCount(id)), CODE: {
	[Feats=setStrProp(Feats, "DieHard",1)]
	[H: output=output+getGroup(id, 1, 1)+", "]
}]  
  
[H: output=output+"<br>"]

[H: id = strfind(statblock, "(?i)Special Attacks? (.*?)(Spells|Spell[ -]Like|Statistics|Tactics)")]
[H, IF(0<getFindCount(id)), CODE: {
	[SpecialATK = trim(getGroup(id, 1, 1))]
	[SpecialATK=substring(SpecialATK,0,min(length(SpecialATK),70))]
	[output=output+"SA: "+SpecialATK+"<br>"]
  };{
	[SpecialATK=""]
}]

[H: id = strfind(statblock, "(Fast Healing )([0-9]+)")]
[H, IF(0<getFindCount(id)),CODE: {
	[FastHealing=getGroup(id, 1, 2)]
	[output=output+"Fast Healing: "+FastHealing+"<br>"]
}]

<!-- Setting Regeneration and Anti Regen Damage Type if present -->
[H: id = strfind(statblock, "(Regeneration )([0-9]+) ([(][a-zA-Z]+[)])?")]
[H, IF(0<getFindCount(id)),CODE: {
	[Regeneration=getGroup(id, 1, 2)]
	[output=output+"Regeneration: "+Regeneration+"<br>"]
	[AntiRegen=if(getGroup(id, 1, 3)=="", "", getGroup(id, 1, 3))]
	[output=output+"Regeneration: "+Regeneration+"<br>"+if(AntiRegen=="", "", "Anti-Regen Damage Type: "+AntiRegen+"<br>")]
}]
  
<!-- ATTACKS BEGIN -->
<!-- Insert empty weapon strings -->
[H: wpnstr=getPropertyDefault( "Weapon0" )]
[H, c(17,""),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|Space|Reach|Special|Ranged|Spell-Like|Spells|Statistic)")]
[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=replace(atkstr, "((\\(.+\\)) and )", "\$2, ")]
	[atkstr=stringToList(atkstr, "(, )",":")]
	[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 (.+?)(?i)(Tactic|Space|Reach|Special|Spell-Like|Spells|Statistic)")]
[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+)(/[+-]\\d+)?( [a-zA-Z]* )?[/+\\-0-9\\s]*?\\((.+)\\)")]
	[IF(0<getFindCount(id)), CODE: {
	[WpnName = "Weapon" + wpnno]

    <!-- Quantity / Weapon Bonus-->
  	[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)+ "'")]
	
		<!-- Weapon Name -->
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"Name", replace(wbonus, " ", "") + if(wbonus=="", "", " ") + getGroup(id, 1, 3) + if(getGroup(id, 1, 6)=="", "", " ("+trim(getGroup(id, 1, 6))+")")) + "'")]
    
	<!-- Manufactured ? -->
  	[id2 = strfind(atkstr, "(?i)(slam|bite|claw|gore|hoof|tentacle|wing|pincer|tail|sting|talon|Constrict)")]
  	[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-SizeM;watkbonus=watkbonus-BAB-StrB-SizeM]
  	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"AtkBonus", watkbonus)+ "'")]

	<!-- CritMult-->
  	[wdam=getGroup(id, 1, 7)]
  	[CritMult=2]
  	[id2 = strfind(wdam, "([0-9]+d[0-9]+)?([+-][0-9]+)?/?([0-9]+-?[0-9]+)?([xX]([0-9]))? ?([a-zA-Z0-9 ]+)?")]
  	[IF(getGroup(id2, 1, 5)==""): CritMult=2; CritMult=replace(getGroup(id2, 1, 5),"[xX]", "")]
  	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"CritMult", CritMult)+ "'")]

	<!-- CritRange-->
  	[CritRange=20]
  	[critFromWdam = getGroup(id2, 1, 3)]
	[critFromWdamsearch = strfind(critFromWdam, "([0-9]+)-20")]
	[IF(0<getFindCount(critFromWdamsearch)): CritRange = getGroup(critFromWdamsearch, 1, 1)]
  	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"CritRange", CritRange)+ "'")]
	
	<!-- CritExtraDamage-->
  	[DmgExtraCrit=""]

	<!-- Damage -->
  	[Damage=if(getGroup(id2, 1, 1) == "", "0", getGroup(id2, 1, 1))]
	[DmgExtra = 0]
	[DmgExtraName = ""]
	[newPlusStr= ""]
	[DamagePlus = ""]
	[DmgExtraCrit = "0d10"]
	[DamagePlus = if(0<getFindCount(id2), getGroup(id2, 1, 6), "")]
	[DmgBonus = 0]
	[DmgBonus = if(0<getFindCount(id2), getGroup(id2, 1, 2), 0)]
  	[id2 = strfind(atkstr, "(?i)(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)")]
	[DmgBonus = if(DmgBonus == "", 0, DmgBonus)]
  	[IF(0<getFindCount(id2)):eval(WpnName + "= '" + setStrProp(eval(WpnName),"TwoHanded", 1)+ "'")]
	[DmgBonus = round(if(0<getFindCount(id2), DmgBonus-Str2hB, DmgBonus-StrB))]
	
  	[ExtraDamageSearch = strfind(DamagePlus, "(.*) ([0-9]+[d0-9]+?) ?([a-zA-Z]+) ?(.*)")]
	[IF(0<getFindCount(ExtraDamageSearch)):DmgExtra=getGroup(ExtraDamageSearch, 1, 2)]
	[IF(0<getFindCount(ExtraDamageSearch)):DmgExtraName=getGroup(ExtraDamageSearch, 1, 3)]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = getGroup(ExtraDamageSearch, 1, 1) + getGroup(ExtraDamageSearch, 1, 4)]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, "plus", "")]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, "and", "")]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = trim(newPlusStr)]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, "  ", " ")]
	[IF(0<getFindCount(ExtraDamageSearch)):newPlusStr = replace(newPlusStr, " ", " plus ")]
	[IF(0<getFindCount(ExtraDamageSearch)):Damage = trim(Damage + if(DmgBonus<0, "", "+") + DmgBonus + " " + IF(newPlusStr != "", "plus " + newPlusStr, newPlusStr)); Damage = trim(Damage + if(DmgBonus<0, "", "+") + DmgBonus + " " + DamagePlus)]
	[burstsearch = strfind(getGroup(id, 1, 3), "([a-zA-Z]+ )([Bb]urst)")]
	[IF(0<getFindCount(burstsearch)): DmgExtraCrit = "1d10 " + trim(getGroup(burstsearch, 1, 1))]
	
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"Damage", Damage)+ "'")]
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"DmgExtra", DmgExtra)+ "'")]
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"DmgExtraName", DmgExtraName)+ "'")]
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"DmgExtraCrit", DmgExtraCrit)+ "'")]
	<!-- Two handed ? -->
    <!-- Default to onehanded weapon -->
	[eval(WpnName + "= '" + setStrProp(eval(WpnName),"TwoHanded", 0)+ "'")]

}]
  
	[wpnno = wpnno +1]
}]

[H: broadcast(strformat("Import Complete %{name}"),"self,gm")]

Post Reply

Return to “D&D 3.5/Pathfinder 1e Campaign Macros”