Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Meta Script] Plugger - give scripts and plugins zero-order priority

April 20 (4 years ago)

Edited May 03 (4 years ago)
timmaugh
Pro
API Scripter

Plugger

FILE LOCATION: My Personal Repo (until one-click submission)

META SCRIPT: This script is a meta-script, and part of the ZeroFrame Meta Toolbox. It can be used on its own, or in conjunction with ZeroFrame and the other meta-toolbox scripts, to modify other scripts' API commands.

ABSTRACT: Plugger gives you the ability to run small scriptlets (or scripts) and grant them zero-order capability (letting them shin-kick their way to the front of the line, no matter when they are installed). With ZeroFrame installed, you can schedule/prioritize the plugin evaluation in the loop, giving you even finer control. The plugins can offer anything from custom dice extraction from an inline roll to distance detection -- anything you might want a script to do in game.

Introduction

You can wrap other scripts you want to run in EVAL tags, and Plugger will evaluate the contents to run that command line. These could be standalone scripts or scripts registered to Plugger as a plug-in. If the scripts is registered as a plugin, you can retrieve whatever data you need to from the game or campaign and manipulate the command line in real time. Effectively, this gives you the ability to run code with the same shin-kicking, queue jumping trick that powers the meta-toolbox.

Although Plugger includes a library with a few built-in functions, the real strength of this sort of plug-ability is that 3rd-party scripters (or even you) can write scriptlets to allow this real-time return of game data to your command line. Need a PageID? Write a script and plug it in. Need the closest X number of tokens to a given token? Or all of the tokens within some given range? Write a script! (Or buy your local scripter a coffee and have them write it for you!)

Syntax Highlights

{& eval}scriptlet(--arg1|val --arg2|val){& /eval}

Using the EVAL Tags

EVAL tags are represented by {& eval} ... {& /eval}. The script command you want to execute would go between these tags. A full EVAL block would be structured using one of these formats:

{& eval} scriptname(arguments for script){& /eval}
{& eval} scriptname arguments for script{& /eval} {& eval} !scriptname arguments for script{& /eval}

The scriptname can represent one of the library of plugin scriptlets, or it could be another script in the game. Everything between the parentheses is passed to that script as arguments.

Use the first form when you want to access a plug-in designed to return information to the line. Plug-ins are built very similarly to normal scripts. In fact, a full-fledged script can be co-opted to return a value, if the developer wishes (more on building plugins later).

The last two forms are functionally the same as each other (the third line simply includes the leading exclamation point), but they differ from the first form in that they do not return anything to the command line. These forms are intended solely for launching other scripts (not plugins) that have nothing to do with returning a value to the command line. The EVAL tag will launch the other script and consume itself, leaving a zero-length footprint behind.

EXAMPLE: All tokens in range

Your character has an attack spell that affects anyone within some given range. Your scripter friend writes a script that returns the IDs of all tokens within that range, telling you that to use it, you would use a command syntax of:

!withinrange range source delimiter

So a typical call might be:

!withinrange 3 @{selected|token_id} ,

…returning a comma-separated list of tokens within 3 units of the given source token. Your scripter friend also tells you that they have built it as a plug-in for Plugger, so now you can use the same scriptlet in-line with another command to substitute in that comma-separated list:

!some-other-script --targets|{& eval}withinrange(3 @{selected|token_id} ,){& /eval} --damage|tons

By the time Plugger gets the message, the token id of the selected token will have been evaluated, and by the time the some-other-script API picks up the message, the EVAL tag will have been evaluated and will now the comma-separated list of tokens within the given 3 unit range.

Nest-able

EVAL tags are nest-able, and are processed from inside-out, allowing you to use one EVAL tag to return a piece of data that would be used as an argument in an enclosing EVAL tag:

{& eval}withinrange(
    {& eval}
        rangecalc(Bob the Slayer, Supernova)
    {& /eval}
    -M1234567890abcdef
    ,
{& /eval}

In that example, the the imagined (and custom built) rangecalc scriptlet figures out the range of the Supernova spell, and returns it to the outer EVAL block’s withinrange scriptlet.

Available Built-In Scriptlet Functions

At the time of writing this, there are two built-in functions, with more coming:

getDiceByVal() will retrieve dice from an inline roll that match a given set of value parameters (i.e., 2|5-6|>=10). Output options are a count of the dice, a total of the dice, or a list separated by a delimiter of your choice. More on the required syntax for this plugin in a later post.

getDiceByPos() will retrieve dice from an inline roll based on the position of the dice based on a set of position parameters (i.e., 2|5-6|>=10). Output options are a count of the dice, a total of the dice, or a list separated by a delimiter of your choice. More info on the required syntax in a later post.

Installing 3rd Party Scriptlets

If you or your local scripter has written a scriptlet to plug into Plugger (which is easy enough to do), you only need to install it as you would any other script. Provided the script author implemented the syntax to register that scriptlet with Plugger, it will be available to you as soon as your sandbox restarts.

April 20 (4 years ago)
timmaugh
Pro
API Scripter

Updates and Releases

April 20 (4 years ago)
timmaugh
Pro
API Scripter

Advanced Usage and Tricks

April 20 (4 years ago)

Edited May 04 (4 years ago)
timmaugh
Pro
API Scripter

Included Plugger Scriptlets

The following plugins ship are included in the latest version of the Plugger script.




getDiceByVal

Retrieves a subset of dice from an inline roll based on testing them against a series of pipe-separated value ranges. Outputs either a count of the number of dice (the default), or a total of the dice, or a delimited list of the dice values (delimiter default is a comma).

BASIC SYNTAX

getDiceByVal( roll rules dicecategory output)

roll can be an inline roll equation (i.e., [[1d10]] ) or a roll marker (i.e., $[[0]])

rules are a pipe-separated list of comparisons for the dice values (see example below)

dicecategory is one of the sorts of dice returned by the inline roll; default = included; can be one of the following:

  • all - all dice
  • included - only the dice that are included (i.e., not dropped, rerolled, etc.)
  • success - critical success dice
  • crit - critical success dice
  • fail - critical failure dice
  • fumble - critical failure dice
  • dropped - the dice that are not included (i.e., those that were rerolled or not kept)

output - whether to output a count, total, or delimited list of the retrieved dice; default = total; declaring this argument requires the dicecategory be explicitly included, as well

EXAMPLE

getDiceByVal( $[[0]] <=2|6-7|>10 included total)

The above would retrieve the included dice from the first inline roll ($0) that were either less-than-or-equal-to 2, between 6 and 7 (inclusive), or greater than 10. It would output the total of those dice.

If you choose a list output, the default delimiter is a comma. You can alter this by using a pipe character followed by the delimiter you wish to include. If your delimiter includes a space, you must enclose it in either single-quotation marks, double-quotation marks, or tick characters.

getDiceByVal( $[[1]] 1|3|5|7|9 dropped list)

The above would output a comma-separated list of odd value die results from the dropped dice of the second inline roll ($1). The following table shows how the delimiter changes based on altering the ‘list’ argument:

ARG         |	EXAMPLE OUTPUT
------------|---------------------
list | 3,7,5,9
list|", " | 3, 7, 5, 9
list|+ | 3+7+5+9
list|` + ` | 3 + 7 + 5 + 9
list| | 3759




getDiceByPos

Retrieves a subset of dice from an inline roll based on testing them against a series of pipe-separated position ranges. Outputs either the total of the number of dice (the default), or a count of the dice (seems pointless, but it’s available), or a delimited list of the dice values (delimiter default is a comma). Dice position is 0-based, so the first die is in position 0, the second in position 1, etc.

BASIC SYNTAX

getDiceByPos( roll rules dicecategory output)

roll can be an inline roll equation (i.e., [[1d10]] ) or a roll marker (i.e., $[[0]])

rules are a pipe-separated list of comparisons for the dice position (see example below)

dicecategory is one of the sorts of dice returned by the inline roll; default = included; can be one of the following:

  • all - all dice
  • included - only the dice that are included (i.e., not dropped, rerolled, etc.)
  • success - critical success dice
  • crit - critical success dice
  • fail - critical failure dice
  • fumble - critical failure dice
  • dropped - the dice that are not included (i.e., those that were rerolled or not kept)

output - whether to output a counttotal, or delimited list of the retrieved dice; default = total; declaring this argument requires the dicecategory be explicitly included, as well

EXAMPLE

getDiceByPos( $[[0]] <=2|6)

The above would retrieve included dice from the first inline roll ($0) that were in positions 0, 1, 2, or 6. It would output the total of those dice.

The same guidelines apply for the list delimiter as for the getDiceByVal plugin, above.

April 20 (4 years ago)
timmaugh
Pro
API Scripter

Writing Your Own Plugin for Plugger

The EVAL tag allows for anyone with a little coding experience to provide an infinite number of extensible features. The EVAL tag will run the script as designated, looking first in its bank of registered plugins. If the script isn’t found there, Plugger will send the script call to the chat to have the script fire that way before continuing with its own processing of the original message.

Remember, only plugins registered to Plugger are handled in sequence, with their result substituted into the command line. If nothing is returned, an empty string will be substituted in place of the EVAL block. Only after the plugin code finishes does Plugger take over again. Calls to outside scripts, on the other hand, are not guaranteed to finish before Plugger moves on.

So, how do you write a scriptlet and register it to APILogic?

Accept a Message

A plugin for Plugger should accept a message object, just as any function that answers a chat event (i.e., handleInput). In fact, your script can also answer a chat event if you like (more on that under Who Called?). The message object will be identical to a message that would be received from a user – it will have properties of whoplayeridcontent, etc. If there were any inline rolls, it will have an inlinerolls array. This will be a copy of the message data that is in Plugger, with the content replaced to be the reconstructed command line that the user would have sent had they invoked your script directly from chat.

In other words, the withinrange script (mentioned above) might require a command line like the following, if it were to be invoked from the chat interface:

!withinrange 3 -M1234567890abcdef ,

When a user places that in an EVAL tag block, they would write:

{& eval}withinrange(3 -M1234567890abcdef ,){& /eval}

If Plugger detects withinrange as a registered plugin in that game, it will hand off a message with the former command line.

Accepting a message might look like this:

const withinrange = (m) => {
log(m.who); // logs who sent the message
};

Parse the content String

As you would with any script, parse the command line to extract the data you require to perform your calculations. If you intend to allow the scriptlet to be called from the command line separate from Plugger, make sure that you verify ownership of the message, as well. This might look like:

const withinrange = (m) => {
    // verify ownership
    if (m.type !== 'api' || !/^!withinrange\s/.test(m.content)) return;
// parse arguments
let [range, sourcetoken, delim] = m.content.split(/\s+/).slice(1);
log(range);
log(sourcetoken);
log(delim);
};

Perform Calculations and Return

Code as you normally would to calculate and arrive at the data you are looking for. When you are done, if you want something to be substituted into the original command line (where Plugger called your plugin), return a stringnumberbigint, or boolean. Anything else (including no return or an undefined return) will be replaced with an empty string.

Who Called?

A message that comes from Plugger to a plugin scriptlet will have one property that a chat-interface-generated or API-generated call will not have: eval. If you want your script to answer both a straight invocation as well as an Plugger implementation, you can differentiate your return based on if you detect this property.

For instance, if a user invokes withinrange from the chat interface, maybe we want to display a small panel of information regarding the tokens that are in the specified range – including their image, name, etc. However, if the call comes from Plugger, you only want to return the token IDs in a delimited string. In that case, once you have arrived at the data, you could test for existence of the eval property, and return accordingly:

let tokens = getTheTokens();
if (msg.eval) return tokens.join(delim);
// if the code continues, you're dealing with a call from the chat interface, not Plugger
// so proceed to build the panel output...

Register to Plugger

The step that turns your script into a Plugger plugin is when your script implements the Plugger.RegisterRule() function in an on('ready'...) block. Here is an example:

on('ready', () => {
    try {
        Plugger.RegisterRule(withinrange);
    } catch (error) {
        log(error);
    }
});

The RegisterRule() function can take any number of functions as parameters, so tack on as many plugins as you’ve written:

Plugger.RegisterRule(withinrange, getclosest, getpageforchar);

Install your script as you normally would, restart your sandbox, and you can immediately start using your script as a meta script.

May 04 (4 years ago)
timmaugh
Pro
API Scripter

Version 1.0.1 Released

May 3, 2021


ADDED: Native handling of line-breaks


Update Notes

Since this meta-script can be used with or without the ZeroFrame Meta Toolbox, it needs to be able to handle line-breaks natively. This update fixes a problem where this script would not have processed correctly were it used in a multi-line macro without ZeroFrame installed, too.

May 07 (4 years ago)

Edited May 11 (4 years ago)
timmaugh
Pro
API Scripter

Version 1.0.3 Released

May 7, 2021


FIXED/ADDED: Tag buffering with parentheses of brace-enclosed tags, i.e., ({& eval })

ADDED: EVAL tags now take escape/deferral character strings between parentheses


Update Notes

Buffering With Parentheses - ({&eval})

This fix gives an option if the command line might result in a series of brace characters that would be inadvertently eaten by Roll20 parsers. In that case, you can enclose the syntax token in parentheses to break up that series of characters. This only matters for brace-enclosed tags:

{& eval}

or

({& eval})

Using Escape/Deferral Strings

Normally, syntax structures that are a part of the meta-toolbox are detected and run in whatever loop order you have established (if you have ZeroFrame installed). If you instead want to defer the resolution of a syntax structure until the script inside the EVAL tag runs, you can now include a character string inside of parentheses as a part of the EVAL tag:

{& eval(^)}getInfoFromClosest({^& if @{selected|bar1}>0}...

There, the ^ character was used as the escape string. The presence of the character in the IF block breaks up the syntax so that the IF isn't caught and parsed within the main message. Plugger will catch the EVAL tag and remove the ^ from the supplied command line before sending that message out to the intended script. By removing the escape character string from the command line, the syntax structure is now detectable when the new message comes through:

{^& if

becomes

{& if
May 11 (4 years ago)
timmaugh
Pro
API Scripter
Plugger has been submitted to the one-click and should be in the next merge.