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

[Help] Modify Inline Roll formatting with API?

1522493170
Finderski
Pro
Sheet Author
Compendium Curator
I'm working on an API script that will need to send an inline roll to the chat, but I'd like to change the formatting of the display of that inline roll result. I know I can do that with roll templates, but is there a way for me to do that via the API? The reason I'd like to use an inline roll in the API output is because the die roll can explode and I'd rather not have to re-create the die roller for that, if I can help it.
1522494210
Jakob
Sheet Author
API Scripter
Finderski said: I'm working on an API script that will need to send an inline roll to the chat, but I'd like to change the formatting of the display of that inline roll result. I know I can do that with roll templates, but is there a way for me to do that via the API? The reason I'd like to use an inline roll in the API output is because the die roll can explode and I'd rather not have to re-create the die roller for that, if I can help it. You cannot  change what the inline roll looks like. What you can do is use the callback for sendChat() to do the roll, parse the result, and then output whatever you want in the callback function (I do that in GroupCheck, it's a bit of a pain to do this, but possible)
1523022764

Edited 1523023037
Finderski
Pro
Sheet Author
Compendium Curator
So, I have this function I created: getDamage = function() {     var rollresult;     rollresult = sendChat("Game","/r 2d6!", function(ops){         var dmgresult = ops[0].content;         //dmgresult = dmgresult.slice(1);         dmgresult = JSON.parse(dmgresult);         log("Damage Result: "+JSON.stringify(dmgresult));         log("Damage Result Total: "+dmgresult.total);         return dmgresult;     });          return rollresult; } The logs in that function let me know I've figured out how to get the roll total, but because sendChat is asynchronous, my rollresult  always comes back as undefined.  I'v heard there's a way to make things somewhat synchronous so I can get the result back prior to moving on...how can I do that?
1523024356
The Aaron
Forum Champion
API Scripter
This is what's usually called "Callback Hell" when writing Javascript (and other callback model asynchronous systems).  Rather than depending on the return result, you instead pass in a callback function to continue processing after the result is ready.  In a larger context, let's imagine you want send a message to the players with the damage result after it's calculated (Note, this doesn't work): on('ready', () => {   const getDamage = function() {     var rollresult;     rollresult = sendChat("Game","/r 2d6!", function(ops){       var dmgresult = ops[0].content;       //dmgresult = dmgresult.slice(1);       dmgresult = JSON.parse(dmgresult);       log("Damage Result: "+JSON.stringify(dmgresult));       log("Damage Result Total: "+dmgresult.total);       return dmgresult;     });      return rollresult;   };   let damage = getDamage();   sendChat('', `Hey players, you took ${damage} damage`); }); because sendChat() is asynchronous, rollresult won't be set until after getDamage has returned.  Instead, you have to pass a callback into your function which gets executed when the data is available: on('ready', () => {   const getDamage = function( callback ) {     var rollresult;     rollresult = sendChat("Game","/r 2d6!", function(ops){       var dmgresult = ops[0].content;       //dmgresult = dmgresult.slice(1);       dmgresult = JSON.parse(dmgresult);       log("Damage Result: "+JSON.stringify(dmgresult));       log("Damage Result Total: "+dmgresult.total);       callback(dmgresult);     });    };   getDamage( (damage) => sendChat('', `Hey players, you took ${damage} damage`) ); }); Depending on what you're doing, this can get a bit out of hand, which is why people often use the randomInteger() function to just roll the dice in the API, but then you have to implement your own exploding and such (which often isn't so hard). Callbacks are kind of the entry level way of dealing with Asynchronous operations.  From there you can move on to Promises (which are kind of a more structured callback system with error handling) and Async/Await functions, which make Promises and asynchronous code look like procedural code. I'd suggest getting comfortable with callbacks first (you're already using the callback model with sendChat() =D ).
1523026304

Edited 1523027178
Finderski
Pro
Sheet Author
Compendium Curator
So, I'm wanting to use that output as a variable that will be put into a STRING which later will be sent to chat....so the last call would it be something like this then: var myDMGTotal; getDamage( (damage) => myDMGTotal ); Edit: So, I tried that, and it didn't work... LOL
1523027443

Edited 1523027626
Jakob
Sheet Author
API Scripter
Finderski said: So, I'm wanting to use that output as a variable that will be put into a STRING which later will be sent to chat....so the last call would it be something like this then: var myDMGTotal; getDamage( (damage) => myDMGTotal ); The Aaron will be around shortly to explain better than me why that doesn't work * EDIT: yes there he is , but it won't. What getDamage is doing is executing   a callback function , not assigning something to a variable. Maybe this rewriting of the example will be instructive: on('ready', () => { const doStuffWithDamage = function(ops) {       var dmgresult = ops[0].content;       //dmgresult = dmgresult.slice(1);       dmgresult = JSON.parse(dmgresult);       log("Damage Result: "+JSON.stringify(dmgresult));       log("Damage Result Total: "+dmgresult.total);       sendChat('', `Hey players, you took ${dmgresult} damage`); } sendChat('', '/r 2d6!', doStuffWithDamage); log('sendChat call sent'); }); Here's  what will be happening: FIRST: the script sends the chat message containing "/r 2d6" (since it has a callback, this is silent and nobody actually sees the message). SECOND: "sendChat call sent" is logged. The main function returns. THIRD: The doStuffWithDamage() function is executed, with the content of the sendChat Message as its "ops" argument.
1523027451

Edited 1523027823
The Aaron
Forum Champion
API Scripter
That's just pushing the same problem up one level.  Side note, I used the "Fat Arrow Syntax" for functions, which is probably something I should explain.  These are all (basically) the same: getDamage( (damage) => sendChat('', `Hey players, you took ${damage} damage`) ); const DealWithDamage = (damage) => sendChat('', `Hey players, you took ${damage} damage`); getDamage(DealWithDamage); DealWithDamage = function (damage) { sendChat('', `Hey players, you took ${damage} damage`); }; getDamage(DealWithDamage); function DealWithDamage(damage) { sendChat('', `Hey players, you took ${damage} damage`); }; getDamage(DealWithDamage); The Fat Arrow => is a way of defining functions concisely: argument => Short Body; argument => { Long Body }; (argument,argument,argument) => Short Body; (argument,argument,argument) => { Long Body }; With a single argument, the ( ) around the argument list can be omitted.  With a short body, the result of the body expression is the return of the function. What you'd do is continue all of your work in the function, like: getDamage( (damage) => { /* do a bunch of stuff */ sendChat('', `Hey players, ${damage}!`); /* do a bunch more stuff */ }); Or better yet, break up what you're doing into many different functions (functional decomposition), and call those from the callback function.  You can see that with asynchronous operations, you start to get heavily nested.  This kind of sums it up: Functional Decomposition will help with that somewhat, as will doing things to minimize the Async callbacks you need to make.  For example, if you need to make 12 rolls, you can just send them all as inline rolls in one async call to sendChat() and iterate over them in the result.  Or for simpler rolls, you can use randomInteger() instead. There's a bunch of places in my scripts where I've done both.  RecursiveTable has a bunch of the async/await style of handling callbacks if you want to look there. GroupInitiative uses a completely different model where it fires off a bunch of async operations with the same callback setup to only execute on the Nth call. And if you want to post your full code or go into details about what you want to do, we can help you figure out a good strategy. (Edit: Ha!  Was 8 SECONDS too slow!)
1523029656
Finderski
Pro
Sheet Author
Compendium Curator
Ok, both of those helped me understand what's going on, but I still can't grok how to apply it to my problem.  So, my script in full can be found at:  SWSolitaire.js (I figured easier to link than post the full thing here) The point of this script is to allow a semi-automated Savage Worlds Solitaire game to be played. It's based off of a card deck (the link above should also provide a link to the full rules I'm basing this API script off of). My goal with this question is to be able to set a variable that can be used in multiple places as part of a bigger output.  The final output will look something like: Where you see the inline roll results, it would be awesome if I could substitute the output of the above function I've been messing around with (which is why I wanted to put it in a variable).  The reason I want to do this is to keep certain things consistent.  It feels weird to have Success = 23 damage, while Failure = 10 damage + whatever else.  So, I want to have a single die roll that would be able to populate multiple places in the output. So, the way this works is: 1. Shuffle the Deck (sets up the cards) 2. Deal (calls the deal function) Deal then gets a card, looks at the face value of the card object and then figures out what to do with it.  If it's a Club, then it calls the encounter function to generate the output of the card above.  It also calls the treasure function to get the treasure output of the card. So, anyway...I hope that helps explain a little better what I'm trying to do.  And while it seemed pretty easy at first, I quickly got into water that's much deeper than my little javascript floaties can safely manage. LOL Thanks for your help so far--I've actually learned a lot and this has been a lot fun.
1523032718

Edited 1523032955
The Aaron
Forum Champion
API Scripter
So, while I think understanding Asynchronous Programming in Javascript is a good goal, to get you moving forward, I'd suggest just changing to using randomInteger() and reimplementing exploding dice.  If all you ever need is 2d6!, you can just replace getDamage with this: getDamage = function() { // define a function that rolls a d6 and handle exploding. // returns an array of the rolled values, like [4] or [6,6,3]     const getD6e = () => {       let acc=[];       while(acc.push(randomInteger(6)) && acc[acc.length-1]===6) /* {} */ ; // empty body       return acc;     }; // define a function that calls getD6e n times and returns an array of all the rolls // for example with n=2: [3,5] or [6,2,5] or [6,2,6,6,3]     const getNd6e = (n) => [...Array(n).keys()].reduce((m)=>[...m,...getD6e()],[]); // sum the array returned for 2d6!     return getNd6e(2).reduce((m,v)=>m+v); }, which will do the exploding d6s and return the sum. getD6e() is a function that will return the result of rolling a d6 and exploding on a 6 as an array of results. getNd6e(n) is a function that will call getD6e() n times and return an array of all the rolls. The last line just adds up all the values in the array and returns it. If you need the rolls and the sum, you could modify it to this version: getDamage = function() {     const getD6e = () => {       let acc=[];       while(acc.push(randomInteger(6)) && acc[acc.length-1]===6);       return acc;     };     const getNd6e = (n) => [...Array(n).keys()].reduce((m)=>[...m,...getD6e()],[]);     let res = {         rolls: getNd6e(2)     };     res.total = res.rolls.reduce((m,v)=>m+v);     return res; }, which just creates an object and fills in the rolls property with an array of rolled values and the total property with the sum.  If you need to roll other than 2 d6, you could add a parameter to getDamage for the number of dice and just replace the 2 passed to getNd6e() with whatever the parameter is. Hope that helps, and if you'd rather pursue the async version, I'm happy to help you with that too.
1523033781
Finderski
Pro
Sheet Author
Compendium Curator
Awesome! I'll give this a try, because even this solution contains things I don't fully understand that I'll need to spend some time with.  I'll get back to you on the async stuff... :)
1523034302
The Aaron
Forum Champion
API Scripter
I'd be happy to go over it in excruciating detail!
1523035188
Finderski
Pro
Sheet Author
Compendium Curator
The Aaron said: I'd be happy to go over it in excruciating detail! Emphasis on "excruciating?" LOL
1523038455
The Aaron
Forum Champion
API Scripter
Totally! Let's take this, the equivalent of 1d6!     const getD6e = () => {       let acc=[];       while(acc.push(randomInteger(6)) && acc[acc.length-1]===6) /* {} */; // empty body       return acc;     }; First off, this is defining the function getD6e().  It's using the const declaration type (see the excruciating discussion below!) because the value of getD6e is never going to get reassigned, it will always be this function. () => { /* ... */ };  Means this is a function taking no arguments. let acc=[]; This declares an array that we'll use for accumulation. I'm using let here, but this could (and probably should) be const as the acc variable will always point to this array, and we will be changing the contents of the array but not the actual array object, so it would be fine. while(acc.push(randomInteger(6)) && acc[acc.length-1]===6); This is where all the work is happening.  Normally, it's a good idea not to use loop constructs without a block because it can be confusing.  However, sometimes it just seems like the most elegant solution, so that's what I did here.  Loops execute the next instruction for each iteration they perform.  That instruction is usually a block denoted by { }, which is a collection of instructions, but is sometimes a single instruction (which I never do because it can too easily cause issues), or it can be and empty instruction denoted by a tailing ; This line could be expanded to this equivalent form: do { acc.push(randomInteger(6)); } while(acc[acc.length-1]===6) this will: 1) Add a random integer from 1-6 to the end of the acc array using the push(value) method. Array.push(value) adds the value to the end and returns the number of items now in the array. 2) Check if the last value in the array, the one just pushed, is a 6.  Since arrays are zero-biased, i.e indexed starting at 0, the last item in the array will have the index (number of items - 1), so acc[acc.length-1] is the last item.  If it is a 6, it will return to step 1 and push on another value. Since Array.push() returns the number of items in the array, it will always be greater than 1, which means it will always evaluate to the truthy value true.   && is the boolean AND operator, which evaluates to true if both sides of the operator are true.  That means that this expression: acc.push(randomInteger(6)) && acc[acc.length-1]===6 Will always be true on the left side (push returns 1 or more) and will be true on the right side if the last value pushed was a 6.  The practical upshot of all that is that the one line while statement: while(acc.push(randomInteger(6)) && acc[acc.length-1]===6); Keeps adding random numbers from 1-6 until the last thing added wasn't a 6.  This does the exploding. The last line just returns the accumulated values in acc:       return acc; Excruciating Detail, round 1! Aside: var vs let vs const These all define variables, var is the old way.  It creates a function scope variable which can be changed anywhere in the function and is "hoisted", meaning it's considered to be defined from the beginning of the function scope.  So: function d6rlt3(){ while(!bar || bar<3) { var bar = randomInteger(6); } return bar; } Even though bar is declared inside the while(){ }, it's declaration is "hoisted" to the top of the function scope.  The first time the while condition bar<3 is checked, the value of bar is undefined, because it has yet to be assigned.  This might not look too weird to you if you've never programmed in something besides javascript, but it is actually quite weird and the source of some strange problems, particularly when the same variable is used across many blocks of code in a function. Contrast that with let , which is block scoped and not hoisted.  This would give you several errors: function d6rlt3(){ while(!bar || bar<3) { let bar = randomInteger(6); } return bar; } First, it would tell you "ReferenceError: bar is not defined" for that while condition of bar<3, if you redefined it to be this: function d6rlt3(){   let valid=false;   while(!valid) {     let bar = randomInteger(6);     valid = (bar>3);   }   return bar; } It would tell you "ReferenceError: bar is not defined" for the return statement.  This all seems like a bunch of messing about for nothing new, but eventually, you land on something like this: function d6rlt3(){   let bar;   do {     bar = randomInteger(6);   } while(bar<3);   return bar; } which is better from a variable and scope point of view (but slightly weird because contrived examples are hard). Finally const is block scoped and not "hoisted" just like let , but it's value can only be assigned once.  This lets the interpreter do nice things with it behind the scenes and also prevents you from accidentally changing something you meant to compare to and the like.   This makes const ideally suited to declaring functions.  The above rewritten with const would break outright because you can't declare a const without assigning it a value (defining): function d6rlt3(){   const bar;   do {     bar = randomInteger(6);   } while(bar<3);   return bar; } This results in "SyntaxError: Missing initializer in const declaration".  Adding an initialization to 0 then goes to the next issue: function d6rlt3(){   const bar = 0;   do {     bar = randomInteger(6);   } while(bar<3);   return bar; } which is: "TypeError: Assignment to constant variable." when you try to assign the result of randomInteger(6) to bar. While slightly tedious if you're used to the freedom of var , this actually protects you in many ways from all sorts of problems.  It's a good habit to get in to always use const when declaring variables by default, then consciously make the decision to relax to using let when you need to change the value at a later point.  And of course, to never use var ever again. =D
1523038755
Jakob
Sheet Author
API Scripter
While slightly tedious if you're used to the freedom of var , this actually protects you in many ways from all sorts of problems. It's a good habit to get in to always use const when declaring variables by default, then consciously make the decision to relax to using let when you need to change the value at a later point. And of course, to never use var ever again. =D ... until the point you get obsessive about writing everything in a functional style and you never have any non-constant variables anymore at all. Joke aside, this is very good advice, of course.
1523039024
The Aaron
Forum Champion
API Scripter
Yeah, I was gonna mention functional programming and the move to immutable data, but I didn't wanna go THAT excruciating! Joking aside the second, I read Purely Functional Data Structures by Chris Okasaki a few years ago and it really changed my perspective on functional programming and organizing data structures even in traditionally procedural languages.  Pretty wild stuff.
1523041008
Finderski
Pro
Sheet Author
Compendium Curator
Uh...I may need to get my eyes checked, because in the middle of reading that, my eyes glazed over and I couldn't understand a thing I was reading... LOL There's a lot to unpack there, but I heard: Never use 'var', always use const and if a variable will need to change, then don't use const, but use 'let' instead. I'll give it a shot. :)
1523041771
The Aaron
Forum Champion
API Scripter
HAHAHA.. Well, let me know when you're ready for round 2. =D
1523041823
Finderski
Pro
Sheet Author
Compendium Curator
The Aaron said: HAHAHA.. Well, let me know when you're ready for round 2. =D Will do, but I think the swelling in my eyes will need to go down first. LOL
1523042129
The Aaron
Forum Champion
API Scripter
=D No worries!
1523042264
Finderski
Pro
Sheet Author
Compendium Curator
So, quick question based on what I've read above... var SWSolitaire = SWSolitaire || (function() {     'use strict';     var version = '0.1',         lastUpdate = '[Last Update: Mar 24, 2018, 7am]',         Round = 0,         chatOutputLength = 4,         deck      = {},         hand      = {},         discards  = {},         mook = {/* stuf in here that won't change */},/*more stuff after this*/ Should that all be changed something like this: const SWSolitaire = SWSolitaire || (function() {     'use strict';     const version = '0.1',         lastUpdate = '[Last Update: Mar 24, 2018, 7am]',         chatOutputLength = 4;     let    Round = 0,         deck      = {},         hand      = {},         discards  = {};     const    mook = {/* stuf in here that won't change */},/*more constant stuff after this*/ Did I understand that right so far?
1523043653
The Aaron
Forum Champion
API Scripter
Almost.  You'd actually drop the SWSolitaire || part, as SWSolitaire can't be redefined with const so you'd never end up in a situation where you need to prevent redefinition: const SWSolitaire = (function() {     'use strict';     const version = '0.1',         lastUpdate = '[Last Update: Mar 24, 2018, 7am]',         chatOutputLength = 4;     let    Round = 0,         deck      = {},         hand      = {},         discards  = {};     const    mook = {/* stuf in here that won't change */},/*more constant stuff after this*/ and in reality deck,hands,discards can probably be const as well, as you probably aren't reassigning a new object to them, you're just manipulating the existing object.  That can be a pretty confusing concept, here's an example: const hand = {}; hand.foo = 23; hand.bar = 3; hand.qux = hand.foo/hand.bar; That's all fine because you're not changing which object hand refers to.  In the above, hand is a reference to the single object that you added a foo, bar and qux property to.  It's still the same object, you haven't changed it out.  To use an analogy: You have a car.  That car is constantly yours.  You can put other people in the car, remove them, change the mirrors, refill the gas, change the radio station, but it's still your car constantly during all of that. This would not work: const hand = {}; hand = { foo: 23, bar: 3 }; hand.qux = hand.foo/hand.bar; because you're creating a whole new object with the properties foo and bar and trying to assign it to the hand variable. Probably fine to leave them as let instead of const though.