How to use HeroLab Functions: Difference between revisions

From RPTools Wiki
Jump to navigation Jump to search
m (Conversion script moved page How to use HeroLab Functions to how to use HeroLab Functions: Converting page titles to lowercase)
m (Taustin moved page how to use HeroLab Functions to How to use HeroLab Functions over redirect)
 
(2 intermediate revisions by the same user not shown)
Line 55: Line 55:
When you've completed this section, you will have macros like this one:
When you've completed this section, you will have macros like this one:


<source lang="mtmacro" line>
<syntaxhighlight lang="mtmacro" line>
[r: fieldsFromElement("AC", "document/public/character/armorclass", "ac,touch,flatfooted",
[r: fieldsFromElement("AC", "document/public/character/armorclass", "ac,touch,flatfooted",
     "NormalAC,TouchAC,FlatfootedAC")]
     "NormalAC,TouchAC,FlatfootedAC")]
</source><br />
</syntaxhighlight><br />


The above call demonstrates multiple techniques (a more formal definition will appear later):
The above call demonstrates multiple techniques (a more formal definition will appear later):
Line 66: Line 66:
** The first parameter is used simply to output a text message at the start and end of the extraction process (just a status message).
** The first parameter is used simply to output a text message at the start and end of the extraction process (just a status message).
** The second parameter is the top-level XML element that contains the elements to be extracted.  It looks like this:
** The second parameter is the top-level XML element that contains the elements to be extracted.  It looks like this:
<source lang="xml">
<syntaxhighlight lang="xml">
<armorclass ac="25" flatfooted="22" fromarmor="+5" fromcharisma=""
<armorclass ac="25" flatfooted="22" fromarmor="+5" fromcharisma=""
     fromdeflect="+1" fromdexterity="+2" fromdodge="+1" frommisc=""
     fromdeflect="+1" fromdexterity="+2" fromdodge="+1" frommisc=""
Line 72: Line 72:
     <situationalmodifiers text=""/>
     <situationalmodifiers text=""/>
</armorclass>
</armorclass>
</source>
</syntaxhighlight>
** The third parameter is a [[String List]] of attributes to be extracted from the XML element.  In this case, there are three.
** The third parameter is a [[String List]] of attributes to be extracted from the XML element.  In this case, there are three.
** The fourth parameter is a String List of token properties where the results should be stored.  The correspond to the attributes being extracted.
** The fourth parameter is a String List of token properties where the results should be stored.  The correspond to the attributes being extracted.
Line 95: Line 95:
Below the code is a description of the macro.
Below the code is a description of the macro.


<source lang="mtmacro" line>
<syntaxhighlight lang="mtmacro" line>
<!--
<!--
Fields from a single element:
Fields from a single element:
Line 122: Line 122:
}]
}]
<br/>
<br/>
</source>
</syntaxhighlight>


* The first two paragraphs are an HTML comment.  This type of comment in MTscript causes the HTML to be output to the chat log.  The only purpose for sending an HTML comment to the chat log is for later review of an archived chat log (the '''File''' menu provides such an option).  Typically, comments should be simple strings embedded within {{code|[h: ]}}.  [[find a wiki page reference for this]]
* The first two paragraphs are an HTML comment.  This type of comment in MTscript causes the HTML to be output to the chat log.  The only purpose for sending an HTML comment to the chat log is for later review of an archived chat log (the '''File''' menu provides such an option).  Typically, comments should be simple strings embedded within {{code|[h: ]}}.  [[find a wiki page reference for this]]
Line 152: Line 152:
Note that the '''name''' attribute for each {{code|attribute}} element (yeah, bad naming!) is unique for each one.
Note that the '''name''' attribute for each {{code|attribute}} element (yeah, bad naming!) is unique for each one.


<source lang="xml">
<syntaxhighlight lang="xml">
<attributes>
<attributes>
     <attribute name="Strength">
     <attribute name="Strength">
Line 179: Line 179:
     </attribute>
     </attribute>
</attributes>
</attributes>
</source>
</syntaxhighlight>


Because the '''name''' attribute is a unique identifier for each of the children under the '''baseAttr''' parent element, we can create a macro that uses the unique field to access elements one at a time.
Because the '''name''' attribute is a unique identifier for each of the children under the '''baseAttr''' parent element, we can create a macro that uses the unique field to access elements one at a time.
Line 187: Line 187:
[[note that this macro exposes a formatting bug in the MediaWiki "source" element on line 5]]
[[note that this macro exposes a formatting bug in the MediaWiki "source" element on line 5]]


<source lang="mtmacro" line>
<syntaxhighlight lang="mtmacro" line>
[h: baseAttr = "document/public/character/attributes/attribute"]
[h: baseAttr = "document/public/character/attributes/attribute"]
[r: SectionHeader("Parsing attributes from " + baseAttr, "blue")] <br/>
[r: SectionHeader("Parsing attributes from " + baseAttr, "blue")] <br/>
Line 196: Line 196:
}]
}]
<br/>
<br/>
</source>
</syntaxhighlight>


* Make a list of all of the '''name''' fields.
* Make a list of all of the '''name''' fields.

Latest revision as of 23:59, 15 March 2023

HeroLab Portfolios in MapTool

MapTool has added support in v1.5 for importing data directly from a HeroLab portfolio. This page discusses two topic related to using that data: how to access heroes (PCs or NPCs) within a HeroLab portfolio, and how to extract raw data from the hero for storage in the properties of a token.

Importing a Hero (PC or NPC) from a HeroLab Portfolio

Prerequisites: how to access assets via the Resource Library

Use the Resource Library to navigate to where the portfolio is stored. The HeroLab portfolio will show up in a directory as though it were a subdirectory (this allows MapTool to access individual heroes within the portfolio).

Click on the portfolio (just as you would a directory) to see the heroes contained within it in the preview area (bottom half) of the Resource Library panel.

Drag a hero from the preview area onto the current map. (The above screen shot shows a portfolio containing three heroes, Kyra, Kyra #2, and Kyra #3.) To verify that all of the data from the portfolio was imported, double-click the token to open the Edit Token dialog. If the import worked, you'll see a tab labeled HeroLab that is available. If it didn't work, the tab is greyed out and inactive. Here's a screen shot of a token without HeroLab data attached to it (notice the last tab is greyed out).

If the import succeeded, click the HeroLab tab to view the data that was read. Here's what the initial view will look like:

The information has been copied from the portfolio into the campaign and attached to the token. However, this is just the raw data — it has not been imported to any of the properties on the token. (It is impossible for MapTool to automate that part of the process, because every game system will have its own concept of which properties are appropriate.)

The XML tab contains all of the raw information from HeroLab regarding the hero.

The next section will discuss how to write macros that will extract the information and store it into properties on a token. The result will look something like this on the Properties tab:

Extracting Raw Data from a Hero

Prerequisites: basic understanding of macros, understanding of user-defined functions (and scope), basic understanding of XML (or HTML), basic understanding of token properties, basic understanding of string lists

In addition, you will want an existing HeroLab portfolio to practice with (or you can use the one attached to this page).

Overview

The macros in this section will produce the properties shown in the screen shots in the previous section. It is doubtful that you (or your GM) will have the exact same set of properties that they want to track, so modifying the code shown here for your own use is to be expected.

When you've completed this section, you will have macros like this one:

[r: fieldsFromElement("AC", "document/public/character/armorclass", "ac,touch,flatfooted",
    "NormalAC,TouchAC,FlatfootedAC")]


The above call demonstrates multiple techniques (a more formal definition will appear later):

  • The function named fieldsFromElement is a user-defined function (a.k.a. UDF).
  • The function is passed four parameters, each of them strings:
    • The first parameter is used simply to output a text message at the start and end of the extraction process (just a status message).
    • The second parameter is the top-level XML element that contains the elements to be extracted. It looks like this:
<armorclass ac="25" flatfooted="22" fromarmor="+5" fromcharisma=""
    fromdeflect="+1" fromdexterity="+2" fromdodge="+1" frommisc=""
    fromnatural="+1" fromshield="" fromsize="" fromwisdom="+5" touch="19">
    <situationalmodifiers text=""/>
</armorclass>
    • The third parameter is a String List of attributes to be extracted from the XML element. In this case, there are three.
    • The fourth parameter is a String List of token properties where the results should be stored. The correspond to the attributes being extracted.
  • The return value will be output as a "regular" string (see the

The return value from the UDF is a log string of what the function has accomplished. For the example function call shown above, the output might look like:

Parsing AC from document/public/character/armorclass
ac = 25 , touch = 19 , flatfooted = 22

Using herolab.XPath

The macro function that allows the above is herolab.XPath(). It accesses the XML data from the HeroLab portfolio and allows searching and extraction of data.

Here is the code contained in the UDF, above. Below the code is a description of the macro.

<!--
Fields from a single element:
    fieldsFromElement("AC", "document/public/character/armorclass", 
        "ac,touch,flatfooted", "NormalAC,TouchAC,FlatfootedAC")

params:  
    descText - text output to console that describes the `baseAttr` content
    baseAttr - the XPath that identifies the top element
    attrList - the list of attributes to retrieve from the element
    propList - the list of properties where teh attribute values should be stored
        (must be the same number of fields as `attrList`)
-->
[h: assert(argCount()==4, "Wrong number of parameters in <code>"+getMacroName()+"</code>")]
[h: descText = arg(0)]
[h: baseAttr = arg(1)]
[h: attrList = arg(2)]
[h: propList = arg(3)]
[h: assert(listCount(attrList)==listCount(propList), "Number of attributes must equal number of properties in <code>"+getMacroName()+"</code> for "+baseAttr)]
[r,if(descText!=""): SectionHeader("Parsing "+descText+" from " + baseAttr, "blue") + "<br/>"]
[count(listCount(attrList), ", "), code: {
    [r: attr = listGet(attrList, roll.count)] =
    [h: xpath = baseAttr + if(attr=="text()","/","/@") + attr]
    [r: value = herolab.XPath(xpath)]
    [h: setProperty(listGet(propList,roll.count), value)]
}]
<br/>
  • The first two paragraphs are an HTML comment. This type of comment in MTscript causes the HTML to be output to the chat log. The only purpose for sending an HTML comment to the chat log is for later review of an archived chat log (the File menu provides such an option). Typically, comments should be simple strings embedded within [h: ]. find a wiki page reference for this
  • The script first verifies that the proper number of parameters were provided. If not, there's no reason to continue executing the script.
  • Parameters are then copied into local variables. This is primarily for readability, but see scope topic of UDFs.
  • There's another check to ensure that the third and fourth parameters contain the same number of elements.
  • If descriptive text was provided, a message is sent to the chat log that describes which macro is starting. This helps in debugging if any of the status messages displayed contain errors. The SectionHeader UDF simply displays a message in the specified color and is not detailed here.
  • Once the above is done, start a loop and process each entry in the list of attributes to extract:
    • Extract and display the next attribute to retrieve
    • Create a search string to use with herolab.XPath(). The string is the result of baseAttr + if(attr=="text()","/","/@") + attr. This combines the base XPath expression (parameter 2) with either / or /@ depending on whether we're trying to extract the text contained within the element or a specific attribute. If parameters 2 and 3 are "document/public/character/armorclass" and "ac,touch,flatfooted", then the result is three iterations of the loop to extract "document/public/character/armorclass/@ac", "document/public/character/armorclass/@touch", and "document/public/character/armorclass/@flatfooted". But in many cases, the data we want is text inside an element instead of an attribute. In that case, we could specify "text()" as parameter 3 (or one element of parameter 3) and the UDF would accommodate.
    • The herolab.XPath() function is actually invoked to retrieve the information.
    • The returned value is stored into the associate token property.

Intermediate-Level Data Extraction

Extracting attribute text (or element text) is fairly simple, as shown above. But when there are multiple elements with very similar XPath locations, the herolab.XPath() function shows limitations. Because it cannot return a nodeset, it is necessary to retrieve a list of strings from identifying subfields, then iterate over that list and extract each one.

This section will develop a second macro for handling more advanced XML structures.

Here is an example of some XML data that represents the ability scores for a Pathfinder hero. (Unimportant elements have been removed from this sample data.) Note that the name attribute for each attribute element (yeah, bad naming!) is unique for each one.

<attributes>
    <attribute name="Strength">
	<attrvalue base="14" modified="16" text="14/16"/>
	<attrbonus base="+2" modified="+3" text="+2/+3"/>
    </attribute>
    <attribute name="Dexterity">
	<attrvalue base="14" modified="14" text="14"/>
	<attrbonus base="+2" modified="+2" text="+2"/>
    </attribute>
    <attribute name="Constitution">
	<attrvalue base="12" modified="14" text="12/14"/>
	<attrbonus base="+1" modified="+2" text="+1/+2"/>
    </attribute>
    <attribute name="Intelligence">
	<attrvalue base="13" modified="13" text="13"/>
	<attrbonus base="+1" modified="+1" text="+1"/>
    </attribute>
    <attribute name="Wisdom">
	<attrvalue base="20" modified="20" text="20"/>
	<attrbonus base="+5" modified="+5" text="+5"/>
    </attribute>
    <attribute name="Charisma">
	<attrvalue base="10" modified="10" text="10"/>
	<attrbonus base="+0" modified="+0" text="0"/>
    </attribute>
</attributes>

Because the name attribute is a unique identifier for each of the children under the baseAttr parent element, we can create a macro that uses the unique field to access elements one at a time.

redo this macro so that it uses the same parameter organization as fieldsFromElement, above

note that this macro exposes a formatting bug in the MediaWiki "source" element on line 5

[h: baseAttr = "document/public/character/attributes/attribute"]
[r: SectionHeader("Parsing attributes from " + baseAttr, "blue")] <br/>
[foreach(attr, herolab.XPath(baseAttr + "/@name"), ", "), code: {
	[r: attr] =
	[r: value = herolab.XPath(baseAttr + "[@name='"+attr+"']/attrvalue/@modified")]
	[h: setProperty(attr, value)]
}]
<br/>
  • Make a list of all of the name fields.
  • Loop around, processing each name field one at a time (and printing the results for verification purposes):
    • Iterate over all attribute elements looking for a particular name.
    • Extract the attrvalue/@modified field (instead of @base because it contains temporary modifiers).
    • Store the result into a token property.

to be continued

Caveats and Gotchas

HeroLab (the application) does not store containership information in the XML in a way that third-party applications can access. This means that when an item is inside of a container, that information is lost (i.e., not available) to MapTool. Thus there is no way to accurately recreate heroes in any third-party application outside of HeroLab itself.

The current implementation of herolab.XPath() only retrieves strings. It cannot retrieve a nodeset (a term used to denote element sets within the XML data) or numeric values. This is a significant limitation because a macro script that wants to access multiple similar elements must instead generate a list of strings based on unique ids stored within the elements, then index into the XML using those strings.