FreeMarker Facet Output
Purpose
This page describes the output available with FreeMarker variables. This replaces both the existing output tokens as well as providing the only access to variables that are visible to output due to the FACT Token and FACTSET Token.
Available Facets
Single Item Facets
- DeityFacet: "pc.deity"
- RaceFacet: "pc.race"
- AlignmentFacet: "pc.alignment"
- SizeFacet: "pc.sizeadjustment"
List/Sequence Facets
- ActiveAbilityFacet: "pc.abilities" (returned as CNAbilitySelection objects)
- EquipmentFacet: "pc.equipment.all"
- EquippedEquipmentFacet: "pc.equipment.equipped"
- KitFacet: "pc.kits"
- CheckFacet: "pc.checks"
- ClassFacet: "pc.classes"
- CompanionModFacet: "pc.companionmods"
- DomainFacet: "pc.domains"
- LanguageFacet: "pc.languages"
- SkillFacet: "pc.skills"
- StatFacet: "pc.stats"
- TemplateFacet: "pc.templates"
- WeaponProfModelFacet: "pc.weaponprofs"
Additional items available
Recognizing that:
- This list contains *no info* about FACT/FACTSET items (since the output of those is controlled by the data control file), these are the *remaining* items available for output:
- This is a working list that is known to be incomplete except where noted.
All CDOMObjects
- key (KEY token)
- source (this is currently "complex" and controlled by preferences - this needs further investigation to determine the proper lifecycle)
- displayname (name at the beginning of the line in the LST file)
Alignment
We believe this list is sufficient to support all known OS.
- Abb (ABB token - also sets the key at this point due to a recent change - eventually this will be a FACT, but that requires a conversion after 6.6)
Deity
We believe this list is sufficient to support all known OS.
- Alignment
- Description (DESC token)
Domain
- Description (DESC token)
Equipment
- Description (DESC token)
Race
We believe this list is sufficient to support all known OS.
- Description (DESC token)
- OutputName (OUTPUTNAME token)
Size
We believe this list is sufficient to support all known OS.
- Abb (ABB token - also sets the key at this point due to a recent change - eventually this will be a FACT, but that requires a conversion after 6.6)
Spell
- Description (DESC token)
Stat
We believe this list is sufficient to support all known OS.
- isnonability
- lockedvalue
- lockedmod
- base
- basemod
- total
- mod
- noequip
- noequipmod
- notemp
- notempmod
Template
We believe this list is sufficient to support all known OS.
- Description (DESC token)
- OutputName (OUTPUTNAME token)
Use in practice
Items
When a single item is called in FreeMarker, it is used as such (this assumes an XML output):
<deity>${pc.deity}</deity>
The "pc." provides context to the Player Character, since there are potentially other sources of information (e.g. about the game mode or the loaded data)
When dealing with individual items (like a Deity), the default is to produce the display name of the item. (not OUTPUTNAME, the Display Name, which is the name used as the first entry on the line in the LST file)
When other parameters of the item are desired, those are added to the end of the entry. For example, if symbol is a FACT on Deity, and it is VISIBLE to output, then you could use
<symbol>${pc.deity.symbol}<symbol>
Lists
When a list is called in FreeMarker is it important to treat it as a list. We distinguish those by using a plural keyword to fetch the items (such as "pc.checks"). In the case of a list, you generally want to increment over the list:
<#list pc.checks as check> <saving_throw>${check}</saving_throw> </#list>
A few things to recognize:
- pc.checks has no ${} around the item. This is because the "#list" already puts the system into a "state of mind" where FreeMarker understands it is dealing with FreeMarker variables and not plain text. Therefore, the ${} is redundant (will cause an error)
- The #list function assigns a new variable, available within the list (in the example above, "check") that can then be treated just like other objects. Therefore, if there was an "MainStat" FACT on a PCCheck, you could do this:
<#list pc.checks as check> <saving_throw> <ability>${check.mainstat}</ability> </saving_throw> </#list>
If you need to increment across a list of objects while maintaining backwards compatibility to the old output tokens, you can use an assignment of a temporary variable (with a Hat Tip to Andrew W)
<#assign checknum = -1 /> <#list pc.checks as check> <#assign checknum = checknum + 1 /> <saving_throw> <ability>${check.mainstat}</ability> <total>${pcstring('CHECK.${checknum}.TOTAL')}</total> </saving_throw> </#list>
Abilities
Abilities are unique in that they are not directly stored in PCGen. They have other characteristics (specifically, Nature, Pool, and the CHOOSE Selection)
In order to appropriately output these items, the pc.abilities list does not contain the Ability itself. Internally to the code, we are storing a combination of 4 things: Pool, Nature, Ability, Selection
- Pool is the AbilityCategory used to select the item (may be Fighter Feat, so this is NOT the Category of the item in the LST file)
- Nature is Virtual, Automatic, Normal
- Ability is the Ability itself
- Selection is the result of a CHOOSE
This "bundle" of 4 items is called a CNAbilitySelection.
We could increment across those, but you increment across the "block of 4" all at once. So we get something like:
<#list pc.abilities as cnas> <#if cnas.nature == "Virtual"> <name>${cnas.ability.name}</name> <description>${cnas.ability.desc}</name> </#if> </#list>
The problem is that if you had, say, Dodge twice, you'd end up with the description twice. Probably not what was intended, so you will need to account for that in how things are written to the output file. This probably deserves some macros to develop this out appropriately.
Programming Design
Static Setup
Model Factory
Each Facet that will be exposed (generally implementing the ItemFacet or SetFacet interfaces) is registered with an OutputDatabase (OutputDB) and a ModelFactory for that Facet is stored by the Output Database. This ModelFactory will be used during runtime
Actor Identification
Each item we wish to output (the value of a StringKey, ObjectKey, FACT, FACTSET, etc.) must have an OutputActor registered before use. For FACT and FACTSET this is controlled by the VISIBLE token in the definition (YES or EXPORT). For other items, it is currently (Jan 2015) controlled by the code.
These registrations occur to the CDOMObjectWrapper. This object stores the actors mapped by the class of the object (PCClass, Deity, etc.). These CAN default to higher level objects (so something registered to CDOMObject.class is usable on a Deity or PCClass)
Runtime Usage
Initial Setup before Export
At runtime, an initial Map is set up for a PlayerCharacter in the ExportHandler. This is done by OutputDB.buildDataModel()
The resulting Map is passed to FreeMarker. The values in this Map are a series of TemplateModel objects (how FreeMarker accesses information). Each of the TemplateModel objects placed into the Map for a Facet contains the CharID of the Character bring processed. This is done by having the ModelFactory objects in the Output Database produce the appropriate TemplateModels for the given CharID.
The TemplateModels are then available for FreeMarker to check whenever an interpolation (e.g. ${deity.symbol}) is encountered in the template
During Export by FreeMarker
During Export, a TemplateModel that contains a reference to a Facet (and which already knows the relevant CharID) may be encountered. The interpolation (like ${deity.align}) is also known.
For example, when ${deity} is called, the interpolation is pulled from the map to get a TemplateModel that contains the DeityFacet and the appropriate CharID. Since it is a single-item Facet (a PC has one Deity) it returns the Deity (CDOMObject) wrapped in a CDOMObjectModel (this wrapping is done by the CDOMObjectWrapper)
The CDOMObjectModel contains the appropriate CDOMObjectWrapperInfo (given to it by CDOMObjectWrapper during the wrapping process). When an "inner" interpolation (${deity.symbol}) is called, the "symbol" portion is passed to the CDOMObjectModel to determine if it can export that item.
This string ("symbol" is checked against the registered OutputActors. If one was registered with the CDOMObjectWrapper, then the appropriate OutputActor is returned. The OutputActor then is fed the CDOMObject (remember - this was known by the CDOMObjectModel), and the process() method produces another TemplateModel. (in the case of Symbol it is likely a TemplateScalarModel wrapping a String).
That process can be recursive (meaning you could get the domains of a deity, increment across the list of domains, and then pull information from the domains).