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

Coding around the problem: multiple same-API calls in an Ability, fired from macro bar, only activates first

June 04 (5 years ago)

Edited June 04 (5 years ago)
timmaugh
Forum Champion
API Scripter

So, I have a nearly finished API script going through alpha testing, and I'm running into an issue. Here's what I'm seeing:

THE PROBLEM:

I can supply a command line that resolves to expected results. I can supply a different command line that utilizes the same API script also resolves to expected results. I can even put those 2 statements in an Ability and run it from the button provided on the sheet, and as expected they run in succession and resolve to the expected results. However, when I then include that Ability on the Macro bar and run the same Ability from there, only the FIRST statement runs. The second begins (I get a preliminary statement hitting the console), but nothing displays to the chat.

MY INTERPRETATION (MAYBE WRONG?):

I am thinking that when executed from the sheet, the lines are sent individually to the chat to maintain some sort of synchronicity, letting the first usage of the API fully finish before the second begins. On the other hand, when sent from the Macro bar, the contents of the Ability are given to the chat in a way that lets them run asynchronously, and the second call to the same API is colliding with the first.

== QUESTION 1 ==

Is that an accurate assessment of what is going on, and is that result (where the 2nd call doesn't output) what could happen if 2 calls to the same script were made concurrently?

CODING AROUND THE PROBLEM

Not knowing if this is an accurate interpretation of the problem, or (if it is) whether or not someone has developed a solution for it, I'm considering ways to work around it. The script is already declared using an emulated namespace:

var heroll = heroll || (function () {
    'use strict';
    ...
})();

...which is what makes me think that when the second call hits the sandbox, it is grabbing the existing script object, which is already processing the first call.

My idea is to somehow either lock the script or to generate a new sub-object if the first is "in use."

Option 1: Waiting Room

A secondary script could periodically poke the primary to see if it is being utilized. If the primary script trips an "in use" flag as one of the first things it does, and then resets it to "not in use" as one of the last things it does, then the Waiting Room script could sit in a holding pattern until it saw the primary come available. Then, like the coworker you hoped to avoid but who was watching your presence indicator like a hawk, the secondary script (Waiting Room) pounces... and it's all "can you jump in this meeting"-this and "problem with the expense form"-that.

---*ahem*---

I am guessing that would look something like:

var heroll = heroll || (function () {
'use strict';
// ...
const setLock = function(lock = true) = {
heroll.locked = lock;
};
const isLocked = function() {                            // publicly exposed in return statement
return heroll.locked || false;
};
const handleInput = function(msg) {
setLock(); // set the lock
// ...
// sendChat...
setLock(false);
};
const registerEventHandlers = function(){
on('chat:message', handleInput);
};
return {                                                 // expose these functions to public interface
RegisterEventHandlers: registerEventHandlers,
IsLocked = isLocked
};
})();
on("ready", function() {
'use strict';
heroll.RegisterEventHandlers();
});

Then the "Waiting Room" script could hold the second call to the API and periodically test heroll.IsLocked until it got a false in return... at which point it would release the second script call.

== QUESTION 2 ==

I'm guessing the Waiting Room would need a while statement, but how would it then feed the next API line to the primary script? If the primary is listening to the chat window as its only source of input, would the Waiting Room have to output the next statement to chat only *after* it sees the primary unlocked? Would it be better to create a "sidedoor" to the primary script, exposed as a public interface, that took the input directly from the Waiting Room (i.e., no !heroll preface required since it is happening within the sandbox instead of through the Chat)?

== QUESTION 3 == 

Am I recreating the wheel in terms of functionality?

OPTION 2: Sub-Object

This one is less defined. I wonder if there is a way for the initial script to detect its own state (in use, not in use), and spin off a new sub-object of the script. I am guessing this might involve hierarchical namespaces, but I've never done that, and therefore not sure it's what I'm looking for. The goal of this approach would be to have each call to the script operate in its own playpen. Ideally, that would be a playpen that was spun up on an ad hoc basis by some unique identifier (time code). Alternately, it could be handled on a limited-inventory fashion, carving out X number of sub-objects, and the script would have to knock on each door in turn to detect if that sub-object was in use, and if so, go to the next, and so on. Once it finds one available, it flips the "in-use" flag so future-concurrent calls to the script can't use that space.

== Question 4 ==

Would this even accomplish the outcome I am looking for? (That Abilities that include multiple calls to the same script, fired from the Macro Bar, no longer collide)

June 04 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Javascript is asynchronous and single threaded.  Whether your two calls are stepping on each other's toes will depend entirely on your implementation.  If you are storing transient information for a command at the "namespace" level, and your processing includes asynchronous calls (such as sendChat() to roll dice, or getting the bio/notes from a character), then the second command may start processing.  The solution is to store transient information in the local scope, taking advantage of Javascript's Closures to separate the data and maintain consistency during their execution.  You shouldn't need to build some sort of locking system to keep the commands executing separately, and doing so would greatly impact the performance of your script.  Without seeing the code to your script, it's hard to diagnose the issue further, but I would suggesting looking for places where running your command "concurrently" could cause issues and address those.

June 04 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

There is no difference as far as the API is concerned when sending commands to chat whether you send it from a macro or an ability, so you're barking up the wrong tree there. It's far more likely that something in the way you are calling it doesnt work as a macro call.

So before talking about possible solutions to the problem, we need to understand the problem.

Can you post the specific commands you tried: the macro form and the ability form. Also the script in its original form. (If it's long, post it in a gist or pastebin, not directly to the forums.) And finally, any error message that appears in chat or the sandbox. We can go from there.


June 04 (5 years ago)
timmaugh
Forum Champion
API Scripter

Actually, GiGs, this is rebuilding/extending your herodc script for the Hero System! I was hoping to get it a little farther before showing you to get your thoughts, but it's close, at this point, so I'd love your feedback, anyway.

I created a Gist, but I'm not sure how to embed it, here.

The command lines, as they appear in the Ability, are fairly straightforward:

!heroll --d:7d6 --t:ha --pn:The Way of Charity --n:grab weapon, STR Roll to take (33 STR) --col:@{Heretic|color1}
!heroll --d:[[33/5]] --t:p --dbody:true --dstun:false --pl:ROLL RESULT --pn:Part 2 (STR Roll) --col:@{Heretic|color1}

The first line should produce something like this:

And the second line should produce something like this:

I get both outputs when I run it from the Ability. When I pin the Ability to the Macro bar and click the button there, however, I only get the first.

Post script: there's a known issue in the current version of this code that the sandbox breaks if you try to recall a roll for a speaker that hasn't stored one yet; I'm working on the fix for that

Post script, the second: If you want my current "care and feeding" documentation for this (how to use/implement), I can provide it. Like I said, I feel like I'm playing with GiG's toy, so I was looking for his thoughts, anyway.

June 04 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

I love the look of the panel. I always wanted to go back and change the layout, but I doubt I'd have come up with anything so nice. 

Looking at your macro text, nothing jumps out at me as the cause of the issue, so it must be something to do with how it is processed in the body of the script.

Please do provide the care and feeding documentation. I'll try to have a detailed look at this today.

I wouldnt write my original script the same way today, I've learned a lot since then. I'm keen to see what someone else has done with it.

June 04 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

The issue is all the references to heroll.XXX where you are assigning a variable on the global object which you then use for processing in other places.  When both commands are running, they will both be changing and reading those global properties and get intertwined.  You need to extract all the transient details you're manipulating (anything you assign a value to during the course of processing a command), and replace them with a storage location in the context of the current command.  You can either handle that by building and passing along some context object during the course of processing the command, or by creating a uniquely keyed registry for your context information and pass that key along to look it up.  Passing a context object will likely be the cleanest solution.

As a side note, that should also lead you to solving the recall issue.  For that, you'd likely need to store sufficient information in a lookup that you can access at a later point.  the Push functionality of MutantYearZero does this, as does the TagMar script (when rolling 3d dice). 

June 04 (5 years ago)

Edited June 04 (5 years ago)
timmaugh
Forum Champion
API Scripter

@The_Aaron... that makes a lot of sense. But I'm not sure the way I see to implement it is going to get me over the finish line (edit: I meant this as a doubt of my skills, not your suggestion). If I understand what you're saying, instead of setting up my heroll.stuff in the setDefaults function (.parameters, .theResult, .theSpeaker, etc.), I would first need to declare a local object, and then give it those properties.

Would that keep a firewall between separate calls to the API?

So, just at the top of my script I would drop a:

let localYokal = {};

...and then replace all of the heroll references that store information that is only necessary to this one roll:

const setDefaults = function() {
    localYokal.parameters = {    
        foo: "bar",
        qud: "zuu",
        ...
    };
    ...
};

(care and feeding documentation to follow).

June 04 (5 years ago)

Edited June 04 (5 years ago)
timmaugh
Forum Champion
API Scripter

=== HEROLLER: CARE AND KEEPING =====

Introduction

Hero Roller (or, heroll, for short), works with the Hero character sheet. It takes a command line of arguments to describe the Hero power you want to use, then spits out all of the parameters that the roll would produce, in game... hopefully in a visually appealing package.

Arguments Are... Our Friends

Each argument is detected by a the presence of a space followed by a double dash in the command line. The following example is 3.5d6 (effectively, 3d6+1d3) with a designation of the normal mechanic and a name of "Doom Smack".

!heroll --d:3.5d6 --mech:n --pname:Doom Smack


Aliases

Each argument has various aliases that can be used in its place. For instance, you can supply the activation roll for a power using the argument activation, act, or even just a. That means that this roll:

!heroll --d:3d6 --a:14

...is the same as...

!heroll --dice:3d6 --activation:14

...and represents a 3d6 power with an activation of 14 or less.

Valid Options

For some arguments, there are only a range of valid options you can pass. For instance, the dkb ("does knockback") argument can only accept: yes, y, no, n, true, t, false, or f. If the supplied value does not pass validation, a default value is substituted in its place. See the section Output Structure: Verbose for the ability to see how the value you supply maps to the value used for the output.

Templates

Templates are collections of the various other arguments that can quickly get you many options at once. For instance, an "Aid" power will work differently than a "Blast." Templates are a starting point, not an end point. Each individual argument can still be explicitly over-ridden -- so that even if your designated template defaults to not doing knockback in an attack, you could override that by invoking the dkb argument and setting it to true. I suggest you try the arguments separately, first, so that you understand what they do, then consult the Template section to see what slat of options each template represents.

!heroll --d:4d6 --t:flash --dkb:true


Basic Roll Structure

Every roll must begin with:

!heroll

...and it should include a dice argument/value pair (see below for roll equation options).

!heroll --d:4d6+1k

The arguments can come in any order following the heroll API invocation.

!heroll --of:sc --t:k --d:2.5d6 --usestr:true --location:random


Dice and the Roll Equation

Heroll breaks the roll equation down into a number of d6, a number of d3, and an adder (it also stores a mechanic shorthand and a rebuilt equation, but we'll get to those in a minute). Add .5 to the d6 portion of the roll to represent a d3. The adder represents the flat value to be add or subtracted from the total derived from the dice being rolled.

ROLL        ==> EVALUATES TO
----------------------------------
3d6         ==>   3d6
2.5d6+1     ==>   2d6+1d3+1
18.5        ==>   18d6+1d3
4d6-1       ==>   4d6 - 1
5.5d6       ==>   5d6+1d3

You do not need to include "d6" in the supplied equation unless you intend to use an adder. In other words, "3" will evaluate as 3d6, but if you need to include a +1 pip adder, you must supply "3d6+1".

Inline Rolls

Heroll doesn't use inline rolls in the typical fashion. Instead, if you use inline rolls or character abilities in your dice or extradice arguments, they should evaluate to a number (i.e., a total), that the heroll engine will interpret as your total number of d6 (see Breakout - Looking Under the Hood).

Mechanic Shorthand

The heroll engine will also look for (and store) a shorthand reference to the mechanic you want to use (l, n, or k). Include a mechanic shorthand to set the mechanic that the engine should use for the roll (see the Mechanic section, below).

ROLL        ==> EVALUATES TO
----------------------------------
3d6k        ==>   3d6       killing
2.5d6+1n    ==>   2d6+1d3+1 normal
4l          ==>   4d6       luck


Fixing Bad Rolls

The following rolls are INVALID:

BAD ROLL    ==> DO THIS INSTEAD
----------------------------------
2d6+1d3k    ==>   2.5d6k
3d3         ==>   1.5d6
5d6normal   ==>   5d6n
-2d6        ==>   contemplate your life choices


Breakout - Looking Under the Hood

Since the amount of d6 interpreted by the engine could be fractional, some processing of the roll equation happens based on your input. Obviously, the integer portion adds to the number of d6 to roll, while the fractional portion gets sent to the d6, d3, or adder values according to the chart, below:

VALUE       ==> MAPS TO
----------------------------------
   <-.333   ==> +1 (adder)
.334-.666   ==> 1d3
.667->      ==> 1d6 - 1

After this, the dice are "normalized". Normalizing flattens multiple adders into more/less d3, and multiple d3 into more/less d6. Just as 2d3 should be treated as 1d6, a +2 adder should result in 1d3. Normalization occurs for both the dice and extradice arguments, as well as when those values are combined.

EXAMPLE: Heretic has an attack that can do 6.5d6 of normal damage, but he can also add his STR dice to the roll. His STR dice are derived in an ability called "GetSTRDice" defined on his character sheet. He sends the following command to the chat:
     !heroll --d:6.5d6 --xd:%{Heretic|GetSTRDice}
The dice argument (6.5d6) is interpreted as 6d6, 1d3, and 0 adder. The GetSTRDice ability reduces to an output of 2.5, which is interpreted as 2d6, 1d3, and 0 adder. Neither of those get changed when normalized, however before the roll is calculated, they are combined into 8d6, 2d3, and 0 adder. This amount of dice gets normalized into 9d6, 0d3, and 0 adder.

This may seem straightforward for a straightforward roll, but understanding how it happens is important to make sense of the ways certain rolls interact. A roll of 12d6-1, when combined with 2.8d6 (producing 2d6-1), does NOT become 14d6-2; it becomes 13.5d6.

Alternate Dice Value: check

Supplying the value of check to the dice argument short-circuits the need to produce any BODY, STUN, KB, or POINTS output. Use this if there is a simple effect that must be rolled for (for example, a martial shove that does only a fixed amount of KB).

!heroll --d:check --t:ha --pn:Get Back Jack --n:does 6m of shove


Output Structure

There are two basic options for structuring the output, as well as a verbose option to add extra diagnostic information.

Tall vs SideCar

The output can fit in a vertical container roughly the default width of the chat window (tall), or it can have an additional "sidecar" (sc) only viewable if you expand your chat window slightly. The tall format will present all of the relevant information in a the same container utilizing more vertical space. The sidecar space presents only the most applicable information (like to-hit and damage information for an attack), and places other information (like the actual dice that were rolled to produce the effect) in the sidecar, for quicker readability. Set this option using the output format argument (of). The default option is tall. Including only the argument name (any of the argument aliases that refer to the outputformat) without supplying a value will instruct the heroll engine to use the sidecar layout.

Verbose

What you supply might not always map to what you expect. In that case, you probably have an error in your command line. Use the verbose argument to include a table of "what you provided" for each argument beside "what it mapped to." The table will not only show you the baked-in parameters that are available to you, it will also show you the parameters that you included which it didn't understand (handy for catching mis-spellings,etc.). This can be a very valuable debugging tool to make sure what you intended to send to the heroll engine is what was actually sent.

Argument List 

The following arguments are available to use with the heroll script engine. Each argument can be aliased with various options (so you don't have to type "--activation" if you would prefer to use the shorter "--a"). Each argument has to pass certain validation checks (ensuring that it is a number, or a boolean true/false, for example), and if the validation fails, certain default values are used instead. The table below lists all of this information.

ArgumentAliasesValid OptionsDefaultNotes
act

a, act, activation

[number]0
diced, dice[see Dice and the Roll Equation]1d6
dbodydb, dbody, doesbodyy, yes, n, no, t, true, f, false[template]whether an attack should report BODY damage; also whether a Points-output should derive points from BODY
dkbdkb, dknockback, doesknockbacky, yes, n, no, t, true, f, false
[template]
dstunds, dstun, doesstuny, yes, n, no, t, true, f, false
[template]whether an attack should report STUN damage; also whether a Points-output should derive points from STUN (default)
extradicexd, xdice, extradice[number]0
kbdicemodxkb, extrakb, kbdice, kbdicemod[number]0

loc

l, loc, locationany, random, none, [hit location], [special shot]none
mechanicrm, rollmech, rollmechanic, m, mech, mechanicn, k, l[template]This can be set in any of three places; see the Mechanic section.
outputformatof, output, format, outputformat, sctall, sctall
pointslabelpl, plbl, plabel, ptsl, ptslbl, ptslabel, pointsl, pointslbl, pointslabel[string][template]
powernamepn, pname, powername[string][template]
primarycolorc, col, color, pc, primarycolor
3- or 6-digit hex, with or without #
[template]
stunmodxs, xstun, extrastun, stunmod[number]0
templatep, pwr, pow, power, t, tmp, template[see table, below]c
useomcvmental, um, omcv, useomcvy, yes, n, no, t, true, f, falsefalse
ocvo, ocv[number]-1supply an OCV to override any checking of a character sheet (including when you don't have a character sheet). This flattens all OCV mods to 0, including any originating from a called-shot. Including the location argument will still invoke BODY and STUN multipliers.
verbosev, verbosey, yes, n, no, t, true, f, falsefalse
recallrc, recalllast, [valid id][current]IN DEVELOPMENT: should recall the previous roll from the state variable, in case you forgot to tick a radio button on your character sheet before calling it, or if you want to see the verbose output for the previous. Use last to get the last person's roll (even if it isn't the current speaker), or no argument to get your own. Eventually, it could also retrieve a roll for a designated target.


Arguments Requiring No Value

Certain arguments don't require an explicit value declaration (following the ":" or "|"), because just by including them you are invoking the "true" state. These are listed below.

ARGUMENT     ==> DEFAULT VALUE
----------------------------------
dbody        ==>   true
dstun        ==>   true
dkb          ==>   true
outputformat ==>   "sc"
useomcv      ==>   true
verbose      ==>   true
recall       ==>   (current speaker)

All aliases of these arguments will behave the same (so sc, an alias of outputformat, will invoke the sidecar layout if included as --sc).

Mechanic

The mechanic argument represents the way dice are totaled in the Hero system. Dice can be totaled as Normal (n), Killing (k), or Luck (l). This is separate from how a power is output to the chat. For instance, an Aid power uses the "normal" mechanic to total the dice, but reports that total as "Points of Aid" (pointslabel argument).

Priority of Assignment

The mechanic can be set in a template default, in an explicit argument, or as a shorthand inclusion in the dice argument (i.e., "3d6+1k" uses the shorthand "k" to trigger the killing mechanic). The order of precedence goes:

template is trumped by mechanic argument is trumped by shorthand

That means that in the following line:

!heroll --d:5d6k --t:b --rm:l

...will evaluate to using the killing mechanic, despite the "blast" template designating the normal mechanic, and the mechanic argument being explicitly set to luck.

Points Based Powers and Mechanic

Certain templates are set up to report as points instead of damage. Those points can be determined from either the STUN or BODY of a die roll. For this, use the doesbody and doesstun arguments. A points-based power will use the normal mechanic to total BODY and STUN. For a points-based output, the doesstun argument tells the roller to use the STUN. Use the doesbody argument to tell the engine to count the BODY, instead. If both are set to true (as they would be for a Healing power that heal both BODY and STUN), the output will show both values. If somehow both arguments are set to false, the engine will behave as if doesstun is true.

Templates

Templates are collections of the above arguments that are tailored for the various powers in the Hero system, describing their most common implementation. They are the starting point to base out a power, setting the default values for the other arguments. Further customization, using explicit arguments, lets you craft various interactions of the arguments to get you to the goal you're looking for. Use the templates to get you close to the set of argument values you are looking for, then change only the arguments that require it (or that you want to).


There is no rule that says you have to use the Aid template to represent an Aid power. Since you're looking for a collection of argument values, you could use exactly the wrong template and then change all of the arguments to their correct value for your character's particular power... though why you'd want to do that, I couldn't say. The one component you should pay attention to is how the template reports (outputas), because that argument is not otherwise exposed to you. That means if you need a power that reports as damage, make sure you start with a template that produces that output (the default template, if none is declared, is a basic attack using the normal mechanic). On the other hand, if you need a power to report as points, you don't want to start with one designed to produce damage (the "p" template is a base points-based output, pulling from the STUN value of the roll).


Consult the tables, below, for the slate of options described by each template.

Custom (default)

alias: c, cust, custom, def, default

outputas	:	attack
powername : Attack
mechanic : n
dbody         : true
dstun         : true
dkb         : true
primarycolor : #b0c4de
pointslabel : -----
useomcv         : false


Aid

alias: a, aid

outputas	:	points
powername : Aid
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #ffaa7b
pointslabel : POINTS OF AID
useomcv         : false


Blast

alias: b, blast

outputas	:	attack
powername : Blast
mechanic : n
dbody         : true
dstun         : true
dkb         : true
primarycolor : #5ac7ff
pointslabel : -----
useomcv         : false


Dispel

alias: di, dispel

outputas	:	points
powername : Dispel
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #b0c4de
pointslabel : POINTS OF DISPEL
useomcv         : false


Drain

alias: dr, drain

outputas	:	points
powername : Drain
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #ffaa7b
pointslabel : POINTS OF DRAIN
useomcv         : false


Entangle

alias: e, entangle

outputas	:	points
powername : Entangle
mechanic : n
dbody         : true
dstun         : false
dkb         : false
primarycolor : #b0c4de
pointslabel : ENTANGLE BODY
useomcv         : false


Flash

alias: f, flash

outputas	:	Points
powername : Flash
mechanic : N
dbody         : True
dstun         : False
dkb         : False
primarycolor : #b0c4de
pointslabel : POINTS OF FLASH
useomcv         : false


Hand Attack

alias: ha, hattack, handattack

outputas	:	attack
powername : Hand Attack
mechanic : n
dbody         : true
dstun         : true
dkb         : true
primarycolor : #0289ce
pointslabel : -----
useomcv         : false


Healing

alias: he, heal, healing

outputas	:	points
powername : Healing
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #ffaa7b
pointslabel : POINTS OF HEALING
useomcv         : false


Killing Attack

alias: hk, rk, k, ka, kattack, killingattack

outputas	:	attack
powername : Killing Attack
mechanic : n
dbody         : true
dstun         : true
dkb         : true
primarycolor : #ff5454
pointslabel : -----
useomcv         : false


Mental Blast

alias: mb, mblast, mentalblast

outputas	:	attack
powername : Mental Blast
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #c284ed
pointslabel : -----
useomcv         : true


Mental Illusions

alias: mi, millusions, illusions, mentalillusions

outputas	:	points
powername : Mental Illusions
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #c284ed
pointslabel : POINTS OF MENTAL ILLUSIONS
useomcv         : true


Mind Control

alias: mc, mcontrol, mindcontrol

outputas	:	points
powername : Mind Control
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #c284ed
pointslabel : POINTS OF MIND CONTROL
useomcv         : true


Points

alias: p, pts, points

outputas	:	points
powername : Points
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #9d41e8
pointslabel : POINTS
useomcv         : false


Transform

alias: t, transform

outputas	:	points
powername : Transform
mechanic : n
dbody         : false
dstun         : true
dkb         : false
primarycolor : #ffaa7b
pointslabel : POINTS OF TRANSFORM
useomcv         : false


Luck

alias: l, luck

outputas	:	points
powername : Luck
mechanic : l
dbody         : false
dstun         : false
dkb         : false
primarycolor : #35e54e
pointslabel : POINTS OF LUCK
useomcv         : false


Understanding the Output

The heroll engine does many things for you, but there are some things that it cannot do. You need to understand the boundaries and limitations so you know what will be left to you to figure either prior to invoking a call to the heroll engine, or after, interpreting the numbers that are presented to you.

Knockback

Knockback Dice

Knockback is modified by a kbdicemod argument. Treat this argument as what you are doing to the knockback dice. In other words, a -1 in this argument means you are rolling 1 less die, and therefore more likely to produce some knockback from your attack. There are many things that can raise or lower the number of dice you roll for knockback, but the only one that has a ready handle in the heroll engine is whether the attack uses the killing roll mechanic (1 extra knockback die). That means you DO NOT need to adjust the default number of knockback dice using the kbdicemod argument just to account for your attack being a Killing Attack. Any other adjustment (for the target being in the air, or in water, or for the attack being a martial attack, etc.) can be passed into the kbdicemod argument (and, honestly, use the shorter xkb alias of that argument; we use the longer version in this manual just for readability for newcomers). You can use a macro to supply the options for this argument, and pick a different one for each invocation of the heroll script.

Reading Knockback Result

Knockback is reported in game "inches," and must be multiplied by 2 to get meters.

Damage Multipliers

STUN Multiplier

The base STUN multiplier (either as provided by the location or from rolling a d3 for location-agnostic attacks) can be nudged up or down by implementing the stunmod argument (again, the shorter version, xs, is your friend). For this argument, you are directly modifying the STUN modifier, so a -1 in the stunmod argument is effectively reducing the appropriate STUN modifier by 1.

Reading Damage Results with Multipliers

If you see a damage multiplier in the results output of your roll, it means that you still have work to do, applying that modifier AFTER target defenses are taken into account. For instance, if you see a multiplier in the STUN results box (and/or in the sidecar if you are using that output), and you have a normal attack, that means that you must subtract the target's applicable defenses before then multiplying the remaining STUN by the multiplier shown. Obviously, if you are implementing location damage in your game, you should be familiar with the order of application.


If your game is not using locations for attack targets (even "random" locations figured if the attack lands), there is only 1 damage multiplier (a STUN multiplier for a killing attack power). It is also the only multiplier that applies fully, regardless of what defenses the target can bring to bear. You WILL NOT see the value of this multiplier in the output (though you can easily figure it out by comparing BODY and STUN values). Again, this means that there is no further figuring required to understand the amount of damage done by the attack.

Dice Color

The dice shown as the pool of dice rolled are in one color for your d6, and in the alternate color for your d3 and/or adder. The actual color (primary vs alternate) is assigned based on the contrastiness (yes, I just made that up) of the color you chose for the primarycolor argument.

Examples and Advanced Implementation

The following implements a 4d6 flash named Cosmic Flare, with notes describing mechanics, and designates a blue color:

!heroll --d:4d6 --t:f --a:14 --pn:Cosmic Flare --n:flash to common sight, AoE (16m) Non-Selective --c:4b688b


Here is a similar implementation except using the check value to not output the results bar. This one also references an attribute called "color1" where the color for the panel is stored (for easy color coding or changing at a later date):

!heroll --d:check --pn:Way of the Mountain --n:does 9m of shove --t:ha --col:@{Heretic|Color1}


The next utilizes the xd (extra dice) argument to read the velocity from the character's sheet and feed that as a modifier to a martial throw. It send the output as the sidecar format:

!heroll --d:6.5d6 --t:ha --pn:Way of the Wind --n:throw, target falls --sc --xd:[[@{Heretic|velocity}/10]] --col:@{Heretic|color1}


Another example using the check value to validate that the character created a wall of given size:

!heroll  --p:pts --pn:Wall of Light --d:check --of:tall --pl:Wall of Light --n:8 PD, 8 ED, 6m long, 4m tall, .5m thick, dismiss, choose either not anchored or mobile --c:@{Prism|pwrcol}


The next example demonstrates a power the player can choose to "push" by a given amount. The player is asked for input, and the result is fed into the xd argument:

!heroll  --p:b --pn:Light Stix --d:10d6 --of:TALL --c:@{Duo|pwrcol} --xd:[[?{Supercharge Points|30}/10]] --n:4m radius select attack, no range, IIF vs PD

June 04 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Each execution of a function creates a new closure, a private scope that only exists for that execution.

const look = ()=>{
    let t;
    log(`t is currently: ${t}`);
    t = randomInteger(100);
    log(`t set to ${t}`);
    setTimeout(()=>log(`t is still ${t}`),1000+randomInteger(2000));
};
[...new Array(10)].forEach(n=>look());

Throw that into the API and observe that "t is currently: undefined" is always what you see in the log, followed by "t is set to X" where X is a random number, and that 1-3 seconds later you see that same random number being reported.

June 04 (5 years ago)
timmaugh
Forum Champion
API Scripter

Excellent, The Aaron! I will revamp and post back with any questions (or hopefully good results). Thanks!

June 04 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

np!

June 04 (5 years ago)

Edited June 04 (5 years ago)
timmaugh
Forum Champion
API Scripter

OK, I have updated the Gist with the current version of the code. You should see (at line 58) that I declared an object to use as the mount point for all of the ephemeral data (thisRoller). All of the properties that used to mount to the heroll object in the global scope now attach to thisRoller, instead. But the 2 calls to the API still only work in the Ability on the character sheet, not from the button of that Ability pinned to the Macro Bar. From there, I only get the first.

Is the problem where I have thisRoller declared? If I understand closures/scope correctly, the declaration is within the closure of the heroll script. I noticed in the past that other objects I declared here did not update between calls to the script. For instance, at line 63, I drop in a few objects I use as lookup tables. These work great... but I had to move one of the tables that used to be here to the setDefaults function (at line 667) because it had 2 entries that were supposed to randomize for every time the script was called (the 'focus' and 'none' entry in that table). Having that object declared off the "root" closure of the script let the once-randomized value for those properties persist between calls... so what was first randomized to be a '2' stayed a 2 forever until I restarted the sandbox. By putting them in the setDefaults function, I seem to have forced them to generate every time, and I thought (from reading other posts of yours) that the reason had to do with globally scoped closures persisting. Do I have that correct? And is that what is befalling me with thisRoller, too?

Where else should I declare it? Within handleInput, and then pass it along?

June 04 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Your thisRoller is no better than using heroll.XXX  You are declaring it in the closure that represents your whole script, which makes it just as global as heroll.  I should have explained this better, but try this script:

const MyScript = (()=>{

	let privateGlobal = {};

	const publicFunc = ()=>{
		log('inside public func');

		log(`privateGlobal.foo = ${privateGlobal.foo}`);
		privateGlobal.foo = randomInteger(100);
		log(`Setting privateGlobal.foo to ${privateGlobal.foo}`);

		let functionLocal = {};

		log(`functionLocal.foo = ${functionLocal.foo}`);
		functionLocal.foo = randomInteger(100);
		log(`Setting functionLocal.foo to ${functionLocal.foo}`);
	};
	
	return {
		publicFunc
	}
})();

on('ready',()=>{
	MyScript.publicFunc();
	setTimeout(MyScript.publicFunc,100);
	setTimeout(MyScript.publicFunc,100);
	setTimeout(MyScript.publicFunc,100);
});
That should illustrate the difference.  Note that privateGlobal retains its value while functionLocal changes for each invocation.
June 04 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Tim R. said:

Where else should I declare it? Within handleInput, and then pass it along?

Yeah, inside handleInput would be ideal.  I think what you're fundamentally missing is the understanding that API scripts are only executed once per sandbox startup.  After that, the only time your code is executed is in response to and event that occurs for which you've registered a a callback function.  The implication is that the function that initializes the heroll object only runs one time, and it's job is to create a closure for all your functions and return the public interface that you share for other scripts to execute, which is what gets stored in the heroll variable.  If you don't have a public interface, you could even omit the heroll object entirely.


June 04 (5 years ago)

Edited June 04 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter



Aaron's help has you covered better than I could, but I wanted to emphasise this bit:

The Aaron said:

If you don't have a public interface, you could even omit the heroll object entirely.

You dont need to load everything you have in a single object. Just to pick an example, on line 667 you have

thisRoller.locationDataTable = { // details for all locations //

There's no need to have this declared in the master object. You could have a function that returns the hit location and process whatever location-related details you need doing. In your master object you only need details for one location, the one that was hit.


Anyway, this looks like a very comprehensive and fun script. Thanks for the overall guide, i look forward to playing with it.


June 05 (5 years ago)
timmaugh
Forum Champion
API Scripter

Aaaaaahhhhhh..... that popping sound you hear is my brain. I think I get it. What Aaron said about the script only running once makes a lot of things make a lot of sense. This is my first real foray into javascript after other languages, so it feels like I'm learning something at every turn... but that's what makes it fun! Thank you both for the help. Looks like another revamp is in order! I will report the update and results as soon as I can reconfigure the code.

And I look forward to any feedback you have, GiGs!

June 05 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Sweet... =D  I highly recommend Javascript: The Good Parts by Douglas Crockford.  It's older, but it's a great base for understanding Javascript if you're coming from another language.

Here's some links that might have good reading about Roll20 API:

June 05 (5 years ago)

Edited June 05 (5 years ago)
timmaugh
Forum Champion
API Scripter

That did it! Moving the thisRoller object to the handleInput function (and passing it as necessary from there) solved my issue. Now the Macro Button (firing an Ability with 2 calls to the script) works as expected. I also carved out the location info GiG and I mentioned (previously line 667); it's no longer tied to the thisRoller object, but just returns the required data. I'll keep looking for similar trims.

Going through this process was good, too, because I found a small smell in my script. My validateInput function should have been just evaluating data, not modifying it... a "quick fix" broke that box, and forced me to rethink my plan.

Here's the updated Gist. Thank you so much for the help, and for the extra reading. There's always more to learn! Now... just a few more tinkerings... 

June 05 (5 years ago)
The Aaron
Roll20 Production Team
API Scripter

Woot!  I love the satisfaction of writing and improving scripts!

June 05 (5 years ago)
timmaugh
Forum Champion
API Scripter

And, of course, I pasted the wrong copy of the code to the gist... and somehow failed to save the work I did locally to re-scope the object.

Which means I get to do it all over again.

I guess I just needed more practice.

June 05 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

oh no!

I was just going to ask about the line 667, because it didnt seem to have changed.

June 05 (5 years ago)
timmaugh
Forum Champion
API Scripter

Yep... just uploaded the re-fixed file. Sorry about that.