This module exists because I'm not good enough at TypeScript to contribute directly to Workbench. It is a collection of macros and helper functions I've written for PF2e. It will (hopefully) be continually expanding. If you have a macro you'd like to contribute, and it's unsuitable for inclusion in Symon's repo (ie, it uses some of the helper functions provided by this module), feel free to open a PR, or open an issue if you don't want to git.
Macros are accessed via game.pf2emhl.macros.
Requires one token selected, and at least one target. Has handling for target limits depending on Performance rank, and will ignore any targets with an effect that contains both "Immun" and "Fascinating Performance" in its name, case-insensitive. TODO: Build immunity effect, apply as appropriate (existing behaviour is a holdover from standalone macro)
Implements the Relic Gift of the same name. Prompts the user to select a weapon, and adds a Strike RE and attendant WeaponPotency, Striking, and AdjustStrike rules to mirror the weapon's property runes. Run again on the same actor to remove the rules. Does not prevent you from using the weapon's base strike while active; No practical way to do that without being more destructive.
For recovering weapons hidden in flags by the old Lashing Currents macro originally shipped with the PF2e Relics module.
Requires one token only selected, and a currently held torch. Creates an Item Pile containing the torch, removing it from the actor. If the torch was lit, apply that light to the resulting pile token. Significant generalization and improvements planned.
Provides a dialog to quickly set which statistic the actors of any/all selected tokens use for initiative. (styling not final)
Helpers are accessed via game.pf2emhl.
These are mostly replacing one-liners, but I got tired of typing out all my error handling for this stuff every time.
Returns a single token placeable, or errors if more than one. If fallback
is true
(default), will attempt to find a token of the user's assigned character on the current scene if no others selected.
Returns an array of selected token placeables, erroring if none selected, unless fallback
is true
, as above.
Returns a single token placeable, targetted by the specified user (defaulting to the user running the macro). Errors if user has no targets, or more than one target. Can override the latter behaviour with the useFirst
parameter, which will suppress errors for >1 target and simply return the first target of the given user.
Returns an array of token placeables, targetted by the specified user, as above. Errors if no targets found.
A reimplementaion of game.i18n.format()
with some extra handling:
- Localization strings which contain curly braces that are not intended to be substitutions are supported via escaping the opening brace (
\{
) - Substitutions that are not provided in
data
but exist in the localization string will default to an empty string (instead ofundefined
) ifdefaultEmpty
istrue
Returns the provided string prepended with either 'a' or 'an' (lowercase), as appropriate.
Returns the provided string with the first character having .toUpperCase()
applied to it.
Returns an Error
with the message having been passed through localize()
as above, and with prefix
prepended to it. if log
is provided and is an object, that whole object is passed to console.error()
for ease of debugging. notify
determines whether or not to produce a banner notification in addition to the console error, and if left nullish will be dictated by the module setting for banner notifications.
localizedBanner(str, data = {}, { notify = null, prefix = "", log = {}, type = "info", console = true} = {})
Localizes str
with data
, preprends prefix
, and calls ui.notifications[type]
with the result and console
. Errors if type
is not info
, warn
, or error
. If notify
is nullish, falls back on the module setting, as above. If log
is provided and is an object, it will be passed to console[type]()
. notify
functions as above.
MHLError(str, data = {}, { notify = null, prefix = "MacroHelperLibrary: ", log = {}, func = null } = {})
A simple wrapper on localizedError
above, pre-fills the prefix for this library's calls, and provides the func
variable which, if provided, is inserted between the prefix and the rest of the error string, for more a more granular 'where did this error come from' report.
Passes loggable
to console[type]()
, with prefix
as a separate argument first for ease of console filtering.
Simple wrapper on the above with a set prefix.
Returns the appropriate value from the level-based DC table, given level
(a number from -1 to 25). Errors if passed a non-number, and defaults to level 25 if passed a number outside its range.
One-liner wrapping an update to actor.system.initiative.statistic
async pickItemFromActor(actor, { itemType = null, otherFilter = null, held = false, title = null, dialogOptions = {}, errorIfEmpty = true } = {})
Utilizes the pickAThingDialog
helper documented below to prompt the user to select an item owned by the provided actor, that matches the provided filters.
itemType
: must be either a valid PF2e item type, or'physical'
, which is also the default.otherFilter
: must be a function that takes one parameter and returns bool (as you would pass to.filter()
)held
: if true, restrict to items currently held by the actor (any # of hands)title
anddialogOptions
are passed through topickAThingDialog
(see above)errorIfEmpty
: if false, will return null if no items matching the given filters is found, otherwise throws an error.
async getAllFromAllowedPacks({ type = "equipment", fields = [], filter = null, strictSourcing = true, fetch = false } = {})
Returns an array of index entries (if fetch
is false
) or Documents (if fetch
is true
), matching the provided filter, while respecting the Compendium Browser's allowed packs and sources settings.
type
: Must be the slug of a compendium browser tab, or one of the allowed aliases:ability
(aliases:action
),bestiary
(aliases:npc
,actor
),campaignFeature
,equipment
,feat
,hazard
, orspell
.fields
: Any data paths your filter requires, beyond the standard compendium indicies for that document type.filter
: Must be a function that takes one parameter and returns bool (as you would pass to.filter()
)strictSourcing
: Iftrue
, will suppress documents with missing source information, otherwise they're let through. Depending on how thorough your content module author(s) is at setting publication data, this could block a significant number of documents.fetch
: Iftrue
, returns whole documents, otherwise (default) returns only the compendium index data. NOTE: if the CB has not yet been initialized when called, there will be significant (couple seconds) delay on first run while that's handled.
Returns bool: Does the provided user have ownership permissions on the provided document? If provided token placeable or document, operates on the associated actor (fallback code stolen from warpgate, thanks honeybadger)
Takes a folder (Document or ID) in root
, and returns a flattened array of ids from every Document in the folder structure. World folders only.
Takes root
as above, and a Document (or any object with an ownership
property) in exemplar
, and applies the latters ownership data to every document in the folder structure. Doesn't handle documents/folders in compendia. Be careful, as once an ownership level has been set for a particular user, it can not be reset to inherit via this method, and will need to be explicitly downgraded if so desired.
async pickAThingDialog({ things = null, title = null, thingType = "Item", dialogOptions = {} } = {})
Provides a dialog to pick from a list of choices. Merges provided dialogOptions
with its defaults ({ jQuery: false, classes: ["pick-a-thing"] }
). title
defaults to Pick a[n] {thingType}
if not provided. things
must be an array of objects that look like:
{
label: <string>,
value: <string>,
img?: <string>,
identifier?: <string>
}
Example image (produced via pickItemFromActor above):
The purple text is the indentifier
, which can be supplied to disambiguated things with duplicate names. Currently produces a single button per provided thing
, regardless of thing count.
TODO: implement select menu fallback for > configurable limit of items, improve styling generally.
Classes are accessed via game.pf2emhl.classes.
MHLDialog is designed to be a drop-in replacement for the foundry Dialog class, with a few improvements:
This is mostly personal preference, but it means that any callbacks you use with this class should assume they will be passed an HTMLElement instead of a jQuery object, unless you specify jQuery:true
in your dialog options object.
In base Dialog, the way Application handles merging the options object, if you specify classes:["my-class"]
as part of your dialog options, it will overwrite the array entirely, removing the "dialog"
class. MHLDialog includes a workaround for this, and adds its own class ("mhldialog"
) to the list in addition to whatever you give it.
Supports passing either a path to a handlebars file (must have extension .html
or .hbs
), or an inline handlebars template string, as content
in dialog data. The template is compiled and then passed the contents of the contentData
property, in addition to the buttons
and content
variables that the base class provides, as well as the idPrefix
variable, which is set to mhldialog-${this.appId}-
. This last allows valid-by-html-rules id
properties on your form inputs, and associated labels, eg:
Supports passing a validator
property along with the dialog data. This can either be:
- A function (that takes the root element (respecting the
jQuery
option, which MHLDialog defaults tofalse
) of the dialog and returns a boolean) - An array of strings equating to the
name
s of form elements that are not allowed to be empty - A single string
name
(gets put into an array and treated as above) If passed either non-function option, the default validator will produce a banner if validation is failed:
getFormsData
takes in html (or jQuery), and, for each form in the data, runs that form through new FormDataExtended
, and assigns the output to an object, with the key of the form's name, eg:
{
"formname1": {
"fieldname1":"value",
"fieldname2":"value"
},
"formname2":{
//etc
}
}
If there is more than one form in html
, and any forms lack a name
attribute, will error. If there's only one form, if that form lacks a name
attribute it will be default to just 'form'.
getFormData
just calls getFormsData
and returns the first form's data; the only difference between it and simple (html) => new FormDataExtended(html).object
is getFormsData
's handling for multiple forms. Either function is suitable as a callback if you'd like to simple dump the form output and handle that separately (my preference over having all the logic in the callback).
As above, takes html/jQuery, returns an object of all valid name/label pairs. That is, for:
<label>
s with validfor
attributes that point to something that has anid
matching thefor
, and aname
, and<label>
s containing a labelable element (button
,input
,meter
,output
,progress
,select
,textarea
) that have aname
, it will produce an object like:
{
"name1":"Label for input name1",
//etc
}
label text is acquired via .innerText
, so may require trimming before use.