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

1591285653

Edited 1591285701
timmaugh
Pro
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)
1591286195
The Aaron
Forum Champion
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.
1591286395
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.
1591288042
timmaugh
Pro
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.
1591288813
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.
1591289853
The Aaron
Forum Champion
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). 
1591294217

Edited 1591297723
timmaugh
Pro
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).
1591297391

Edited 1591301918
timmaugh
Pro
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. Argument Aliases Valid Options Default Notes act a,  act,  activation [number] 0 dice d, dice [see  Dice and the Roll Equation ] 1d6 dbody db, dbody, doesbody y, 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 dkb dkb, dknockback, doesknockback y, yes, n, no, t, true, f, false [template] dstun ds, dstun, doesstun y, 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) extradice xd, xdice, extradice [number] 0 kbdicemod xkb, extrakb, kbdice, kbdicemod [number] 0 loc l, loc, location any, random, none, [hit location], [special shot] none mechanic rm, rollmech, rollmechanic, m, mech, mechanic n, k, l [template] This can be set in any of three places; see the Mechanic section. outputformat of, output, format, outputformat, sc tall, sc tall pointslabel pl, plbl, plabel, ptsl, ptslbl, ptslabel, pointsl, pointslbl, pointslabel [string] [template] powername pn, pname, powername [string] [template] primarycolor c,  col,  color, pc,  primarycolor 3- or 6-digit hex, with or without # [template] stunmod xs, xstun, extrastun, stunmod [number] 0 template p, pwr, pow, power, t, tmp, template [see table, below] c useomcv mental, um, omcv, useomcv y, yes, n, no, t, true, f, false false ocv o, ocv [number] -1 supply 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. verbose v, verbose y, yes, n, no, t, true, f, false false recall rc, recall last, [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
1591297803
The Aaron
Forum Champion
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.
1591298719
timmaugh
Pro
API Scripter
Excellent, The Aaron! I will revamp and post back with any questions (or hopefully good results). Thanks!
1591299272
The Aaron
Forum Champion
API Scripter
np!
1591306439

Edited 1591307143
timmaugh
Pro
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?
1591308562
The Aaron
Forum Champion
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.
1591309107
The Aaron
Forum Champion
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.
1591310320

Edited 1591310346
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.
1591318220
timmaugh
Pro
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!
1591320711
The Aaron
Forum Champion
API Scripter
Sweet... =D&nbsp; I highly recommend Javascript: The Good Parts by Douglas Crockford .&nbsp; 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: <a href="https://app.roll20.net/forum/post/6605115/namespaces-novice-seeks-help-exploring-the-revealing-module-pattern" rel="nofollow">https://app.roll20.net/forum/post/6605115/namespaces-novice-seeks-help-exploring-the-revealing-module-pattern</a> <a href="https://app.roll20.net/forum/post/6584105/creating-an-object-that-holds-specific-character-dot-id-and-character-name/?pagenum=1" rel="nofollow">https://app.roll20.net/forum/post/6584105/creating-an-object-that-holds-specific-character-dot-id-and-character-name/?pagenum=1</a> <a href="https://app.roll20.net/forum/post/6237754/slug%7D" rel="nofollow">https://app.roll20.net/forum/post/6237754/slug%7D</a>
1591334392

Edited 1591334584
timmaugh
Pro
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...&nbsp;
1591364458
The Aaron
Forum Champion
API Scripter
Woot!&nbsp; I love the satisfaction of writing and improving scripts!
1591386832
timmaugh
Pro
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.
1591388828
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.
1591392013
timmaugh
Pro
API Scripter
Yep... just uploaded the re-fixed file. Sorry about that.