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

Running API to Affect Tokens/Graphic Objects: Where Do I Look in Roll20 Documentation?

Hi, I'm moving on to create procedures that can look at tokens and do things based on their attributes.&nbsp; All I'm able to find are: <a href="https://roll20.zendesk.com/hc/en-us/articles/360037772773#the-script-editor-0-0" rel="nofollow">https://roll20.zendesk.com/hc/en-us/articles/360037772773#the-script-editor-0-0</a> <a href="https://roll20.zendesk.com/hc/en-us/articles/360037772793#API:Objects-Path" rel="nofollow">https://roll20.zendesk.com/hc/en-us/articles/360037772793#API:Objects-Path</a> Is there anywhere else in the documentation I can look?&nbsp; It's loads of fun to move a token and have it move a bit more.&nbsp; But that only seems to occur on the onChange event but I want to write an API that will look at a selected token and do stuff!&nbsp; I suspect there is something I can do with the on ('chat:message'..) but I want to find the documentation they have on how to apply it to a selected token. If anyone can point me in the correct direction, it would be much appreciated. Thanks in advanced, -- Tim
1595464402
timmaugh
Pro
API Scripter
You're in the right place for getting info with those articles. Two more places to look are in this forum (lots of good info already written up) and in other scripts to see how other people do it. I would suggest you start to think about what you want to accomplish, and think about it in specific ways: ...what is the trigger for the event ...who can trigger ut? ...what will the event do ...what objects will it need access to in order to do it Events and objects are both covered in the documentation, but sometimes the best lessons I learned were the things not covered there. So what are you imagining your script could do? Maybe we can give some more specific places to look...
1595502672

Edited 1595503266
timmaugh said: You're in the right place for getting info with those articles. Two more places to look are in this forum (lots of good info already written up) and in other scripts to see how other people do it. I would suggest you start to think about what you want to accomplish, and think about it in specific ways: ...what is the trigger for the event ...who can trigger ut? ...what will the event do ...what objects will it need access to in order to do it Events and objects are both covered in the documentation, but sometimes the best lessons I learned were the things not covered there. So what are you imagining your script could do? Maybe we can give some more specific places to look... OK, thanks Tim.&nbsp; I want to create one or more buttons on a macro bar or as a token button that will mark the specific range of a piece on a map.&nbsp; As one example, the button would highlight the cells (hexes in this case) indicating the range of that an artillery or mortar unit can reach and provide fire support to any friendly units in contact with enemy.&nbsp; That highlighting could be an all around aura, but preferably the actual hex cells to the "front": of a token. The button would call an API that would loop through all selected tokens and for each one: 1) Evaluate if the token character's Unit_Type is "mor" or "arty" (mortar or artillery); 2) If true, get the token's bar value (2) for artillery range - 12 k in the example shown; 3) Turn on a circular aura, or, much preferably, highlight cells (hexes) in the front 60 degree arc to represent 2. The big thing for me is how do I set up a procedure that rec ognizes selected tokens?&nbsp; Sure, the initial launch would be an on chat event with the command !arty: &nbsp;on('chat:message', function(msg) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(msg.type == 'api' &amp;&amp; chatcommand.startsWith('!arty')) { I understand how loops work of course, but how to I enumerate or call the properties of a&nbsp; selected token? I've tried using the example On Change event from the first link in my opening post as a stand alone API script in order to get started: on("change:graphic", function(obj) { sendChat("Graphic",obj.name + ' ' + obj._id) }); But all I get is this: That's what prompted me to ask my initial question. Getting back to artillery ranges: The aura option is easy enough using The Aaron's Tokenmod script and a character abilty. An example follows of a character sheet and a selected mortar platoon with the aura on.&nbsp; The artillery battery selected in the previous screen shot had a range of 12 k while the mortar platoon shown below has a range of only 2 km. However, I'd like to be able to indicate which hex face a token is facing and highlight only those hexes at which it can fire - an set up battery of 4 guns would only be able to fire in the direction of three hex faces, not all around like an aura indicates. There, you asked for it, Tim :)&nbsp; I hope that was straightforward. All the best, -- Tim
1595513266

Edited 1595514173
David M.
Pro
API Scripter
Something like this will give access to a token object associated with the selected token if(msg.type=="api" &amp;&amp; msg.content.indexOf("!arty")==0) &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var selected = msg.selected; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (selected===undefined) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("API","You must first select a token"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var tok = getObj("graphic",selected[0]._id); }; Seems like you might have to start getting into creating "paths" to draw your highlighted hex regions. This got a little confusing for me for my first api script which did something similar (I was drawing features on the dynamic lighting layer based on the selected token properties). It gets into JSON strings and such which I had no previous experience with. This is what I did, in case it helps for your application. I'm sure it's not the prettiest, but it works.&nbsp;&nbsp; <a href="https://app.roll20.net/forum/post/8908015/magical-darkness-script-via-dynamic-lighting-layer" rel="nofollow">https://app.roll20.net/forum/post/8908015/magical-darkness-script-via-dynamic-lighting-layer</a> Basically, for may case I grabbed the selected token properties (including location, height/width), then did some math to build a JSON string describing a grid within a circle circumscribing the rectangular boundaries of that token. The createObj("path", {&lt;object&gt;})&nbsp; block is what actually created the drawing. Seems like it would be a similar approach for you, just with different math and keeping things on the token layer to allow easy deletion after its use. EDIT: Imagine you would need to pass some arguments (e.g. range, etc.) to your api call from chat. Below is a common way to handle that. This creates an array of arguments (args) everywhere a "--" exists in your api call. Example: if the selected token has a bar_1 value of 10, then "!arty --3 --@{selected|bar_1} " would create a three element array ["!arty", "3", "10"] in your script. The .shift removes the first element "!arty", then you do stuff with all the rest. Note you probably have to convert the number strings into an integer to start doing any math with it. let args = msg.content.split(/\s+--/); args.shift(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; if (args.length &gt;= 1) { //Do some stuff with the arguments... };
1595516129

Edited 1595538580
timmaugh
Pro
API Scripter
Alright, Tim... you're getting there. Let's start here , which is the API reference for Objects in Roll20. Sorry if any of this punches below your weight class, but if someone else comes along, at least it will all be in one location. If you want the tl;dr version, jump to the end... Getting Objects Before you can do anything with objects, you have to get them. Couple of ways to do that... you can get a single object with getObj(), you can&nbsp;return a bunch of objects that match your parameters with findObjs, or they can be "selected" at the time of a chat message. findObjs findObjs() is a R20 function that returns an array. You give it an object with properties, and it uses those properties to match any objects it finds. let t = findObjs({ type: "graphic" }); findObjs() is optimized with an index, so it's quicker than another R20 function, filterObjs() . In fact, I have yet to encounter a case where I had to use filterObjs(). I prefer to get the array of objects from findObjs() and then filter using native javascript operations: let t = findObjs({ type: "graphic" }).filter( g =&gt; g.get("top") &lt; 200 &amp;&amp; g.get("left") &lt; 200); So, what's happening there? findObjs() is returning an array of all "graphic" objects in the game. filter() takes that array and feeds them, one at a time, designating each as "g", into a check. If the object passes both tests, it stays in the array. The end result of the line is still an array of objects, so you either need to snag one: t[0] ...or do collection-level processes like map, filter, reduce, _.each, or loop constructs like while or for. let t = findObjs({ type: "graphic" }).map( g =&gt; [g.name, g.get("left"), g.get("right")] ); ...would give you an array of arrays. Each entry would be in the form of [token name, left position, right position]. Sometimes you want to find a particular object. You have the token_id that uniquely identifies it, and you've put the token_id in a variable called tid. In that case, you still return an array from the findObjs() functions, so you have to get the first element (the only element). let t = findObjs({ type: "graphic", id: tid })[0]; Here, t is a single token object with all of the properties and methods you would expect. Of course, can you be sure when you write your code that this particular Object exists? There are many ways to check if the findObjs() function returned anything that matched. You could check the array length: let t = findObjs({ .... }); if (t.length) { // do things with the returns... Note you don't want to check the array only (instead of the length property). An array is an object, so by checking it by name, you are seeing if the array exists, not if it has entries... // don't do this to see if the array has anything in it let t = [];&nbsp; &nbsp; // empty array if (t) { // this will run because t exists, even though it's empty Another option is to force the return to have something by providing a second option that will always pass. Then you can check whether you have that default return or true R20 objects. let character = findObjs({ type: 'character', id: cid })[0] || { fail: true }; if(character.hasOwnProperty("fail)) return;&nbsp; &nbsp; // if the fail property exists, this is not a R20 character Note, the hasOwnProperty check shouldn't be used like the above "in the wild" beyond R20... the hasOwnProperty method can be overwritten for the object in question, leading to faulty results and problems. Even in R20 you can use the bulkier construct from the Object prototype, so it is good practice to start to see it as this: if (Object.prototype.hasOwnProperty.call(character, "fail")) return; What is happening with the double-pipe in the findObjs() line, above? That is an "OR" construction. Javascript is going to return the first truthy thing it finds that satisfies the instructions you give it. If findObjs() finds no characters to satisfy the parameters (in this case, where you have provided a character id), it will return an empty array. When you ask for the zero-th item from that array, it returns 'undefined', which is a falsey value. In that case, it proceeds to the next check, which you know will pass because you are providing an object. In this way, you could provide a hierarchy of preference of ways to identify the thing. If the user provided a single input (call it 's') that could be either a character id, character name, or token id, you could check them in order to return the first thing it finds. character = findObjs({ type: 'character', id: s })[0] || &nbsp;&nbsp;&nbsp;&nbsp;findObjs({ type: 'character' }).filter((chr) =&gt; { return chr.get('name') === s; })[0] || findObjs({ type: 'character', id: (getObj("graphic", s) || { get: () =&gt; { return "" } }).get("represents") })[0] || { fail: true }; One last pro-tip... if you give the default object you provide a property that mimics the thing you need from the object you return, you can sometimes tighten the code a bit... in the third line of the code just above, I mimic the "get" method of an object to return an empty string so that I don't have to break out of the logical structure to stop for a test. selected Another way to get the tokens you want is if they are selected. Their designation as "selected" is detected as part of the chat event, so you need them to be selected when your api script is called. The chat event comes with a message object, so your handling function should expect to receive an object as an argument. on('chat:message', (msg) =&gt; { // ...}); read more about events... Incidentally, that function structure is fine for smaller scripts with small opportunities for collisions with other code running in your game. As your coding gets more complex or you see it used in games with more scripts that might start to collide with each other (naming things the same, etc.), you should look at the revealing module pattern &nbsp;as a way to keep your variables localized to a pseudo-namespace and avoid problems. Let's talk about what you can do with the message object. You can see a couple of them dissected here , but I'd only start looking at that as you get more comfortable working with them to do basic stuff like... msg.content = the text of the line that reached the chat; by this time, all R20 compressions are completed (reducing attribute calls, inline rolls, etc.) msg.who = who sent the chat msg.selected = the tokens selected at the time of the chat event So that's where you find the selected tokens. The selected property is an array, so you can do things with it that you would normally do to an array. For instance, here is a function where I return a delimited list of the token ids: const listsel = ({ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m: m,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // msg object d: d = " "&nbsp; &nbsp; &nbsp; &nbsp;// delimiter (default is space) &nbsp; &nbsp; } = {}) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; return m.selected.reduce((a, v) =&gt; { return a + v.id + d; }, "").trim(); &nbsp; &nbsp; }; What's going on there? We use reduce() to turn the entire array into a single value. We do that by stepping through the array entries, using 'v' to refer to each entry in the array as we move through it. Since each token has an 'id' property available to us, we can get that with v.id. Concatenate our delimiter (defaulted in the function definition to be a space), and concatenate that to our aggregator/collector (a). In the end, we are left with a string of token_ids for the selected tokens, separated by whatever delimiter we provided. Getting Properties "... now that we've found love what are we gonna do.... with iiiiiiit?" Ahem. '80s flashback, there, sorry. Now that you have your objects, what are you going to do with them? How do you get the properties? For that you can reference the API:Object&nbsp; reference to see what properties each object provides. Some properties are read-only and are promoted to be returnable directly from the object when you have retrieved it. These are designated by the underscore as the first character of the property name. Id is an example... let character = findObjs({ type: "character" })[0]; log(character.id); You can access an underscore property at either version (with or without the underscore), though Aaron recently made a good case for using the non-underscore version in case something changes in the R20 codeisphere where the property becomes writable. In that case, the underscore version might go away. The non-underscore properties (the writable properties) of an object are only available via "get" and "set". let character = findObjs({ type: "character" id: cid })[0]; log(character.get("name")); Certain properties (like handout notes, handout gmnotes, character bios, etc.) are only available asynchronously, which is another whole can of worms I wouldn't open just yet. Not until you need to. And even then, avoid it. Unless forced. Then go charging into the dark with your teeth bared, a snarl on your lips and a joke in your heart, laughing at the capricious, asynchronous gods. Fools, the lot of them. *ahem* Where was I? Right, if you need something asynchronously, post back and we can talk it through. Otherwise, onward! Object Properties vs. Object Properties I think a lot of people who, like me, came from another language or coding environment expect the attributes, abilities, and other elements of a character to... BE... attributes of the character object. They are not. That is, if your character has an attribute of "Strength", you CANNOT access it through some native object construction like: // these will not work log(character.strength); log(character.attributes.strength); log(character.attributes["strength"]); Instead, you have to think of all the attributes, abilities, etc. as objects in their own right. That means that the ONLY properties they actually have are the ones listed in the&nbsp; API:Object&nbsp; reference. Each attribute, ability, etc. has a property pointing to the associated character, so if you want to find an attribute, you need to return the attribute object you're looking for using the character. Here is a function that looks for an attribute for a character and creates it if it doesn't exist: const getCharacterAttr = (attr, charid) =&gt; { let attrDefaultTable = { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Strength": 1, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Mojo": 0, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"DanceMoxie": 10 &nbsp;&nbsp;&nbsp;&nbsp;} let defvalue = attrDefaultTable[attr]; let retAttr = findObjs({ type: 'attribute', characterid: charid, name: attr })[0] || &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; createObj("attribute", { name: attr, current: defvalue, characterid: charid }); retAttr.currval = retAttr.get('current') || defvalue; return retAttr; }; So, again, you can see that we are returning arrays and getting a single item, OR we're creating the item we need. Since creating the item is done asynchronously, we have to include an OR case in where we set the currval (the value won't be there until all of the synchronous code has completed, so... *shakes fist at asynchronous gods*... "Oh, you small, small creatures! I pity your pain! But I bear it. I bear it, unbroken!") Roll20 also gives a getAttrByName function that ties the character id to the attribute and returns current, max, or default&nbsp; values for that attribute. It does NOT return the attribute object, itself... but that's good enough for what you want to do... so, on to what you want to do! Your Specific Issue OK, so back to your issue. You want to: ...select tokens ...get the associated character sheets ...make sure they are artillery or mortar types ...check their range ...manipulate their auras in distance (range) and angle (arc). In psuedo code, you're going to: let batteries = msg.selected &nbsp;&nbsp;&nbsp;&nbsp;.filter( t =&gt; return true if the associated character sheet shows a type of artillery OR mortar ) &nbsp;&nbsp;&nbsp;&nbsp;.forEach( manipulate the aura ) So... air-coding and UNTESTED... here's how you filter for mortars and artillery... let c; let batteries = msg.selected &nbsp;&nbsp;&nbsp;&nbsp;.filter( t = &gt; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;c = findObjs({ type: 'character', id: t.get("represents") })[0] || { id: "" }; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ['mor', 'arty'].includes(getAttrByName(c.id, 'Unit_Type', 'current')); &nbsp;&nbsp;&nbsp;&nbsp;}); Which can be compacted further by putting that findObjs() statement in place of c in the return line, and then dispensing with the brackets: let batteries = msg.selected .filter( t = &gt; ['mor', 'arty'].includes(getAttrByName((findObjs({ type: 'character', id: t.get("represents") })[0] || { id: "" }), 'Unit_Type', 'current'))); Again, untested, but it should work. Once you've got the proper tokens, you can go on to set the aura as needed. I know your explanation of what you are trying to do uses Aaron's script, and you thought to send multiple chat lines to call it for each token, but at this point, you have the tokens you need, so you might as well just change their properties yourself. You can see on the documentation of the token object that a token has properties for: rotation aura1_radius aura1_color aura2_radius aura2_color bar1_value bar1_max (and bar2 &amp; bar3) ...among others... I'm not sure you can highlight the hexes you are able to target, but there are a couple of options, here... You could use the token's light to demonstrate a field of view. You can set the angle to be 60 degrees oriented around the rotation of the token, and make sure it is the right length to illuminate the field of fire. Or you could programmatically create lines on the map (setting some attribute to tie each line to an artillery token) (EDIT: like what David is talking about, above). Two lines per token could draw the cone of the field of fire. You could have "hide" methods to hide them all, and/or "redraw" methods in case a token rotates (changing a graphic sends a "graphic:change" event you can listen for). Lots of ideas... but this is a lot of information already, so take what you need and give it a whirl. Post back if you have questions! Good luck!
1595520140
David M.
Pro
API Scripter
Timmaugh's light/vision/aura approach sounds like a much simpler solution, especially given the re-draw concern brought up in the previous post. The vision would automatically rotate with the token without any additional coding (just need a macro using TokenMod or similar to revert back to original sight). I missed that in the OP and subconsciously zeroed in on the "highlighting" part (since I just recently dealt with it), which requires more math and dealing with constructing the JSON for the path.
David M. said: Something like this will give access to a token object associated with the selected token timmaugh said: Alright, Tim... you're getting there. Let's start here , which is the API reference for Objects in Roll20. Guys, I'm sorry I have not responded sooner, but I have been messing around with the information you've given me and learning a fair bit. Timmaugh, this should be an entry in a manual, thank you for the time you took to write this.&nbsp; I'll be digging more deeply into it.&nbsp; For now, while I have not worked on aura or area of effect, yet, I have been using David's method to access character and token data.&nbsp; In the screen shot below, Unit_Type is from the associated character journal entry and the Character Unit_Type is the character attribute that identifies whether or not the character is cpable of indirect fire. Being a relational database guy, I may eventually combine arty, sparty, and mor into just "indirect", but we'll have to see how things work out. Here's the code - I wan on('ready', () =&gt; { on('chat:message', function(msg) { let sender = 'player|' + msg.playerid; if(msg.type=="api" &amp;&amp; msg.content.indexOf("!arty")==0) { let selected = msg.selected; if (selected===undefined) { sendChat(sender,"=========&lt;br&gt;**Error: Indirect Range Function**&lt;br&gt;Select an artillery or mortar unit."); return; } let tok = getObj("graphic",selected[0]._id); let charid = tok.get("represents"); let unit = getObj("character", charid); let unittype = getAttrByName(charid, "Unit_Type") //Some unit characers do not have unit_type defined as an attribute, //set unit type to 'no' if (unittype == undefined) { unittype = "no"; } else { unittype = unittype.toLowerCase(); } if (unittype !== "sparty" &amp;&amp; unittype !== "arty" &amp;&amp; unittype !== "mor") { sendChat(sender, "=========&lt;br&gt;**Indirect Range Function**&lt;br&gt;" + "*" + tok.get("name") + " (" + unit.get("name") + ") is neither an artillery nor a mortar unit. It therefore does not " + "have indirect fire capablity.*"); return; } let stren = tok.get("bar3_max"); switch (unittype) { case "mor": stren = stren + " mortars"; break; case "sparty": stren = stren + " self-propelled guns"; break; default: stren = stren + " guns/howitzers"; } sendChat(sender, "=========&lt;br&gt;**Indirect Range Function**" + "&lt;br&gt;Unit: " + tok.get("name") + "&lt;br&gt;Unit Type: " + unit.get("name") + "&lt;br&gt;*Character Unit_Type: " + unittype + "&lt;br&gt;*Range: " + tok.get("bar2_value") + "&lt;br&gt;Strength: " + tok.get("bar3_value") + " of " + stren ); }; }); }); Hmmm, just realized I can't have Soviets running around without their rocket launchers - Katyushka - may have to make a new unit. Now to dig a lot deeper into Timmaugh's response. All the best and thanks again. -- Tim
1595797678

Edited 1595860763
timmaugh
Pro
API Scripter
Looks like you're making progress, Tim. Here's a couple of small points... Lines like this: stren = stren + " mortars"; ...can be written more succinctly: stren += " mortars"; And you should always use the 3-character version of comparison operators rather than the 2-character version. That is, use "===" rather than "==" and use "!==" rather than "!=". The joke around this goes that you should use the 3-char version until you know the difference, and when you know the difference, you'll always use the 3-char version. =) The difference is that the 3-char version includes a type-comparison as part of the equality check. Without that type check, you can get some unexpected results. Consider: let s = null; if (s==undefined) console.log(`This ain't right!`); That will run and you will get your message, but s is NOT undefined. It is simply null.
timmaugh said: Looks like you're making progress, Tim. Here's a couple of small points... Ah, the += notation.&nbsp; I've seen it in some of the documentation and was not sure what it was.&nbsp; Nice.&nbsp; wilco.&nbsp; Do other languages use this sort of construct? You and The Aaron mentioned have recommended the triple notation before.&nbsp; Not 100% sure I fully understand it, but will begin to use it.&nbsp; Where we don't define variables by type (number types, string, boolean, etc) it seems like the === or !== also evaluates type as well as value? Is undefined the same as null? Thank you. -- Tim
1595806812
The Aaron
Roll20 Production Team
API Scripter
You might like this latest script I released:&nbsp; <a href="https://app.roll20.net/forum/post/8996556/script-weaponarcs-visible-arcs-that-follow-your-token-and-maintain-their-relative-rotation" rel="nofollow">https://app.roll20.net/forum/post/8996556/script-weaponarcs-visible-arcs-that-follow-your-token-and-maintain-their-relative-rotation</a>
The Aaron said: You might like this latest script I released:&nbsp; <a href="https://app.roll20.net/forum/post/8996556/script-weaponarcs-visible-arcs-that-follow-your-token-and-maintain-their-relative-rotation" rel="nofollow">https://app.roll20.net/forum/post/8996556/script-weaponarcs-visible-arcs-that-follow-your-token-and-maintain-their-relative-rotation</a> I swore I replied to this an hour ago - the server seemed to be having some problems. Anyway, I caught this yesterday and was delighted to see it.&nbsp; The arcs would fit in nicely with what I would like to do. I still haven't figured out whether I want to use aura or cell/hex shading.&nbsp; I had no idea that an aura could be divided. Thanks!
1595821875
The Aaron
Roll20 Production Team
API Scripter
It's not an aura, it's a path object! =D
The Aaron said: It's not an aura, it's a path object! =D
1595830040

Edited 1595830053
The Aaron
Roll20 Production Team
API Scripter
An undocumented feature of paths is that you can set an RRGGBBAA color value and get transparency. =D
The Aaron said: An undocumented feature of paths is that you can set an RRGGBBAA color value and get transparency. =D I've copied it over already.&nbsp; It's absolutely wonderful.&nbsp; Now I will figure out how to call it from within my script above as the range argument will be dependant on the individual tokens versus a standard for the "character".&nbsp; There seem to be a fair number if relevant discussions already. -- Tim
1595852494
David M.
Pro
API Scripter
Tim, you should be able to just pass the token properties directly into the !weapon-arc call. So if "bar1" that had a value of "2" and the units were in km, you'd have something like:&nbsp; !weapon-arc {{ --clear &nbsp;--add 90|45 @{selected|bar1}km }} This script is very cool!
David M. said: Tim, you should be able to just pass the token properties directly into the !weapon-arc call. So if "bar1" that had a value of "2" and the units were in km, you'd have something like:&nbsp; !weapon-arc {{ --clear &nbsp;--add 90|45 @{selected|bar1}km }} This script is very cool! That's a great way to do it.&nbsp; Unfortunately, I have to make sure it does not work for non-artillery/mortar tokens.&nbsp; Ideally, I'd like to be able to call weapon-arc, somehow, from my script. I suppose I could modify Aaron's script.&nbsp; I've looked at it and I think I've figured out where to do it.&nbsp; But I'm not comfortable with doing that.
1595860726
timmaugh
Pro
API Scripter
Tim M said: Ah, the += notation.&nbsp; I've seen it in some of the documentation and was not sure what it was.&nbsp; Nice.&nbsp; wilco.&nbsp; Do other languages use this sort of construct? Yes... but to answer it properly, you first have to differentiate between a concatenation operator (stringA += stringB) versus an arithmetic assignment operator (numA += numB). As a concatenation operator, VB, C#, and PHP support a variant of the operator (PHP uses '.='). And I don't work in the following languages, but I understand that C, C++, and Java at least support the arithmetic assignment version (if not concatenation, too). For the languages that support the arithmetic operator, they'll support the '-=', '*=', '/=' versions, too, to evaluate simple operations immediately. The benefit (aside from simpler code when you're familiar with reading it) is that the operand on the left is only evaluated once. Tim M said: You and The Aaron mentioned have recommended the triple notation before.&nbsp; Not 100% sure I fully understand it, but will begin to use it.&nbsp; Where we don't define variables by type (number types, string, boolean, etc) it seems like the === or !== also evaluates type as well as value? Is undefined the same as null? Undefined is not the same as null. Javascript evaluates a loose version of true/false referred to as "truthy" and "falsy", and it has a rather high number of things that evaluate to "falsy"... including both undefined and null, but those 2 do have a separate meaning. Consider: let s = { prop: 3 }; let t = { prop: null }; let u = {}; console.log(s.prop);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// outputs: 3 console.log(t.prop);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// outputs: null console.log(u.prop);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// outputs: undefined Null and undefined are both primitives, but null must be assigned as a special value for "nothing" (as I did for t.prop, above). That is the difference to keep in mind when you're considering the ways the two values will behave in code. For instance, given something like this: const spanky = (s = 'kronky') =&gt; { &nbsp; &nbsp; return s; }; let nullVal = null; let undefVal; log(spanky(nullVal));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// gives a null value return log(spanky(undefVal));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// gives a string "kronky" return&nbsp; The null value, since it acts like a value, never lets the spanky() function use the default value of s. Undefined does.
1595861657
timmaugh
Pro
API Scripter
The various "equal" operators were confusing to me at first, too. =&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp; &nbsp; assignment operator (sets left side equal to right) != or ==&nbsp; &nbsp;:&nbsp; &nbsp; checks "truthy" match, uses type coercion !== or ===&nbsp; :&nbsp; &nbsp; checks exact match, including type The rules around type coercion can be complicated, so it is often easier to just anticipate what you are testing and use the 3-char versions. That said, a lot of times you don't have to worry about either operator if you're working in an if() condition: let a = []; if (a) log('I passed the test. I will go into the west, and remain Galadriel.'); if (a.length) log('And I shall take many array elements with me.'); Galadriel *won't* take array elements with her, because the array is empty. The first test, above, is simply testing the object for truthiness. It passes. The second is testing the length property of the array object for truthiness... and since 0 is a falsy value, it fails. Both of these tests were performed without the explicit use of any equality comparison operator. Now, as a former VB coder, I'll say that this construction has tripped me up more than I'd like to admit: let s = 'honky'; if (s = 'blinky') log(s); This will always pass, because what you're really testing in your if() condition is 'blinky', which has a truthy value. Along the way, you happen to be asisgning it to s... which you then log and you get 'blinky'. This is where you need the 3-char, type-checking version of the equality comparison operator: if (s === 'blinky') log(s);
timmaugh said: Yes... but to answer it properly, you first have to differentiate between a concatenation operator (stringA += stringB) versus an arithmetic assignment operator (numA += numB). As a concatenation operator, VB, C#, and PHP support a variant of the operator (PHP uses '.='). And I&nbsp; Wonderful, thanks for both answers.&nbsp; I started working in databases back in 1988 (dBase III+ and Oracle at the time, but did little with the latter except stare stupidly at the screen - if "derp" had been invented back then, I would have said that a lot) when the relational model was starting to gain traction. Most of my years afterwards, when I knew what I was doing, I was all relational: Oracle, MySql, Omnis, Access (Jet), and Filemaker Pro - never really got into objects in Oracle.&nbsp; So I'm used to dealing with nulls in that area without the "undefined" part of things. :)&nbsp; &nbsp; -- Tim
timmaugh said: The various "equal" operators were confusing to me at first, too. The blinky thing kind of blows my mind. In: let s = 'honky'; if (s = 'blinky') log(s); Did you mean if (s == 'blinky')?&nbsp; I know I've thrown errors anytime I forgot about the == or ===. PS, I think from a couple of searches on calling scripts from other scripts I saw your name.&nbsp; Do either you or&nbsp; David M , have any how I might call The Aaron's weapon-arc command from within the script I wrote above?&nbsp;&nbsp; -- Tim
1595864531
The Aaron
Roll20 Production Team
API Scripter
Since it operates on selected tokens, there's not a way to invoke it from another API script via sendChat().&nbsp; I can expose a method on it that would let you call it directly and specify tokens and operations.
1595865650

Edited 1595865794
timmaugh
Pro
API Scripter
Right... sorry... my point was that anytime I reverted to my instinctual "to check if a equals b you write 'if a=b'" I was breaking my code. Look at this: let spanky = 'kronky'; if (spanky = null) console.log(`INSIDE THE IF: spanky is ${spanky}`); console.log(`OUTSIDE THE IF: spanky is ${spanky}`); Here, the if() condition is actually testing the right side of the assignment to see if the condition passes. Since null is falsey, you never see the line "INSIDE THE IF..." hit the log. The tricky thing is that you are rewriting the value of spanky along the way, so you do see the line: "OUTSIDE THE IF: spanky is null" The flipside of the above is if you give the right side of the assignment a truthy value. Then you'll get 2 outputs: let spanky = 'kronky'; if (spanky = 'just the kronk') console.log(`INSIDE THE IF: spanky is ${spanky}`); console.log(`OUTSIDE THE IF: spanky is ${spanky}`); This more often manifests as a problem if you are seeing code run from INSIDE a conditional that should not be, based on what you know the value of the variables to be. That could be a sign that you've used the assignment operator rather than a comparison operator. Of course, you can utilize the fact that the left side of the assignment operator gets assigned the right side value, too. Here's something Aaron recently helped me see in a command line parser I am developing: const firstOf = (...args) =&gt; { &nbsp;&nbsp;&nbsp;&nbsp;let ret; &nbsp;&nbsp;&nbsp;&nbsp;args.find(f =&gt; ret = f(), ret); &nbsp;&nbsp;&nbsp;&nbsp;return ret; }; In this case, I want to keep track of the result of the function I pass as an argument, but I only need it if the function evaluates to "truthy". So "f =&gt;..." feeds each element of the args array (the 'rest' array) into the function where the element is run as a function... "f()". Being on the right side of the assignment operator, the evaluated function f() is what is tested for the find() operation. Along the way, we track it in ret. When f() evaluates to truthy, the second argument of the find() operation (", ret") delivers ret (which, besides being truthy, also contains the result of the f() function that just matched). Then we return ret. As for using Aaron's script, you can output an api call in your sendChat, and it will fire off the series of chat events as per usual. So if you have a given token (and all of the pieces of info regarding angle, range, etc), and if weaponarc took a token_id as a parameter, you could just substitute '!weaponarc ... ' in for the text portion of your last sendChat... built appropriately according to the structure it expects. I would suggest using the tick marks to bound your string, as it will give you access to your variables via the ${spanky} construction. let spanky = 'KRONKY'; log(`This is bounded by ticks, so when I type ${spanky} it will give me the value.`); log(`I can also do things like ${spanky.toLowerCase()} to have it computed.`); // Or I can choose portions of what to output... log(`I can tell you that spanky is ${spanky.toLowerCase() === 'kronky' ? '' : 'not '} kronky. It's a cleaner construction. EDIT: seeing Aaron's comment about weaponarc using the selected token, you'll have to wait until he exposes the method or a parameter for you to handle.
1595877827
David M.
Pro
API Scripter
Tim M said: That's a great way to do it.&nbsp; Unfortunately, I have to make sure it does not work for non-artillery/mortar tokens.&nbsp; Ideally, I'd like to be able to call weapon-arc, somehow, from my script. Do the tokens "represent" characters? You could use @{selected|&lt;attribute name&gt;} instead of a token property. You could even add a custom attribute to the sheet and refer it it instead. The powercards script also gives some limited capability for conditionals in your macros.&nbsp; Another option would be to make the call to !weapon-arc a token action ability on only the character sheets you want. Or maybe I'm not understanding the problem completely (likely).
The Aaron said: Since it operates on selected tokens, there's not a way to invoke it from another API script via sendChat().&nbsp; I can expose a method on it that would let you call it directly and specify tokens and operations. No, I don't want you to do anything extra on account of me.&nbsp; I'm going to develop an API that will allow a user to select all the tokens of a certain type and then a separate button to call weapon-arc.&nbsp; I'll also create a clear button that can be used for any token when people select the wrong units anc create an area of effect.&nbsp;&nbsp; I'm playing our D&amp;D group atm and the poor DM has great difficulty using shift-ping, and he'll be a player when we start the wargame campaign.&nbsp; LOL Thanks Aaron. :) -- Tim
1595894676
David M.
Pro
API Scripter
Ahh, I see. Though, I'm not sure if you can actually perform a select directly from the API. You can find all objects of a certain type and then do things with them through your script, but I don't think "select" is one of those things. I will however defer to Aaron's experience over my own previous forum searches.&nbsp;
1595899537

Edited 1595900125
David M. said: Do the tokens "represent" characters? You could use @{selected|&lt;attribute name&gt;} instead of a token property. You could even add a custom attribute to the sheet and refer it it instead. The powercards script also gives some limited capability for conditionals in your macros.&nbsp; Another option would be to make the call to !weapon-arc a token action ability on only the character sheets you want. Or maybe I'm not understanding the problem completely (likely). Although we're not doing it in our D&amp;D campaign atm, I think the tokens are like monster characters?&nbsp; You have a character for an orc and drag a whole bunch of them onto the tabletop.&nbsp; So there's a character for towed artillery, and both sides will have multiple units of towed artillery tokens based on the towed artillery character.&nbsp; The names of tokens will be what differentiate tokens.&nbsp; In the picture, below, there are 3 infantry company tokens, but there is only one infantry company character in the journal: Yes, I'll be having the weapon-arc as a function only for artillery/mortar units.&nbsp; The problem is if you have other units selected, the weapon-arc will be available.&nbsp; I think I'll just have a weapon-arc clear as an option for ALL characters. So doing what you suggested I'll have these two macros based on the artillery and mortar characters as "Show as token action", whereas the clear area macro will be "Show as token action" for all characters: This should work, thanks so much for your help. -- Tim
1595908735
timmaugh
Pro
API Scripter
The Aaron said: Since it operates on selected tokens, there's not a way to invoke it from another API script via sendChat().&nbsp; I can expose a method on it that would let you call it directly and specify tokens and operations. Does a message generated from the api (via sendChat) construct its own selected property to the message object? Or does the array of selected tokens stick around between the initial call (in this case, Tim's script) and the internally generated sendChat message? Is selected still selected?
1595915003
The Aaron
Roll20 Production Team
API Scripter
A sendChat() message from the API has no .selected property at all.
1595940193
timmaugh
Pro
API Scripter
Had not run into that. I wonder why that would be necessary.
1595941034
The Aaron
Roll20 Production Team
API Scripter
It's less about necessity and more about the semantics of the implementation.&nbsp; The selected array is built by querying the UI for all the currently selected objects.&nbsp; The API has no UI, so no selected objects.&nbsp; Same reason @{target|...}, @{selected|...}, and ?{query|...} don't work.
1595941101

Edited 1595941410
timmaugh
Pro
API Scripter
on('ready', () =&gt; { &nbsp; &nbsp; state.message = { selected: [] }; }; on('chat:message', (msg) =&gt; { &nbsp; &nbsp; if(msg.type === 'api') msg.selected.length ? state.message.selected = msg.selected : msg.selected = state.message.selected; }); ...as the first script in your game. Would that get you around the problem? ...ish. "Around-ish" the problem? EDIT: tested the length property, rather than the existence of selected.
1595941285

Edited 1595941913
timmaugh
Pro
API Scripter
But the API wouldn't need a UI of its own if the existing UI exposed a method for getting the selected objects. ...or I'm not understanding the relationship between the API and the UI...
1595943834
The Aaron
Roll20 Production Team
API Scripter
It's something you can get around, but not without adjusting the individual scripts.
1595951453

Edited 1595952954
timmaugh
Pro
API Scripter
What about this for a workaround that doesn't require adjusting other scripts? on('ready', () =&gt; { state.message = {}; }); on('chat:message', (msg) =&gt; { if(msg.type === 'api') { if (msg.playerid !== 'API') { if(Object.prototype.hasOwnProperty.call(msg, "selected")) { state.message.selected = msg.selected; } else { delete state.message.selected; } } else { if (state.message.selected) msg.selected = state.message.selected; } } }); As long as that is in your script library before the other scripts, this should give you the ability to call api scripts from other scripts and still preserve the selected object. Here's a quick test: // ================ IMPLEMENTATION ================ // enter !callThat into chat // ================================================ on('chat:message', (msg) =&gt; { if (msg.type === 'api' &amp;&amp; /^!callThat$/.test(msg.content)) { sendChat(msg.who,"!That"); } }); on('chat:message', (msg) =&gt; { if (msg.type === 'api' &amp;&amp; /^!That$/.test(msg.content)) { sendChat('API',`I detected ${msg.selected ? msg.selected.length : 'no'} selected things.`); } }); To test it using just the above, just type "!callThat" into the chat. The only response you'll get is from the "That" script. Select or deselect things and neither the user-generated "callThat" nor the api-generated "That" should know or care about the difference. ...unless I'm missing something?
1595951870
The Aaron
Roll20 Production Team
API Scripter
Oooh, that's clever!&nbsp; That would totally work.
1595954757
timmaugh
Pro
API Scripter
Me, after I've made a thing.
1595965358
David M.
Pro
API Scripter
That's neat! Could we also just use that code block at the beginning of a given script to potentially call itself recursively? I'm thinking of a specific case for me right now where I'd like to use selected tokens but also pass target tokenIDs as an optional argument to the script. Currently, target tokens in msg causes msg.selected to be ignored thanks to a bug in the API. I'm using a workaround right now, but this would be an interesting option.&nbsp; So in that case, check for target tokens first and if (true &amp;&amp; msg.selected !==undefined), store the selected tokens in the state object, strip out the msg.selected from content, and call itself again through sendChat with the remaining arguments?&nbsp; timmaugh said: What about this for a workaround that doesn't require adjusting other scripts? on('ready', () =&gt; { state.message = {}; }); on('chat:message', (msg) =&gt; { if(msg.type === 'api') { if (msg.playerid !== 'API') { if(Object.prototype.hasOwnProperty.call(msg, "selected")) { state.message.selected = msg.selected; } else { delete state.message.selected; } } else { if (state.message.selected) msg.selected = state.message.selected; } } });
1595970166
timmaugh
Pro
API Scripter
I could see that working. If it was going to be script-specific, you'd probably want it below/within the test for the api handle so that it didn't fire every time a chat api went through -- just for the one in question. If it detects a call to this script, then test the playerid and proceed from there whether it is a player calling it or the api.
1595972534

Edited 1595977164
The Aaron
Roll20 Production Team
API Scripter
I don't think that technique would work for the target/selected problem. You have to run two commands, one without target anywhere in the same submission, and one with it. &nbsp;One thing you may be missing in you case is this: !my-script gets selected !my-script @{target|token_id} In a single chat/macro/ability will still not have a .selected for either api command.&nbsp;
1595973381

Edited 1595973734
David M.
Pro
API Scripter
EDIT -- Apologies to Tim M for kinda hijacking his thread. If I have further questions, I'll start a new one. EDIT 2 -- cross posted with Aaron again. Darn. Though I could maybe include a target "flag" in the first and add the target query directly into the second call? EDIT 3 -- I just violated Edit 1. That didn't take long. Groovy, and good point. Also, I suppose if I have sendChats whispering to the playerID "who" near the end of the script, I would need to store that playerID in the state object as well for the upcoming recursive call. Curious: Do you think the recursive approach is bad practice? My current workaround is to require the user to include a "selectedID|@{selected|token_id}" argument if they also supply a target, which works but is kinda janky and puts more onus on the user.&nbsp;
1595974455

Edited 1595977233
David M. said: EDIT -- Apologies to Tim M for kinda hijacking his thread. If I have further questions, I'll start a new one. EDIT 2 -- cross posted with Aaron again. Darn. Though I could maybe include a target "flag" in the first and add the target query directly into the second call? EDIT 3 -- I just violated Edit 1. That didn't take long. Groovy, and good point. Also, I suppose if I have sendChats whispering to the playerID "who" near the end of the script, I would need to store that playerID in the state object as well for the upcoming recursive call. Curious: Do you think the recursive approach is bad practice? My current workaround is to require the user to include a "selectedID|@{selected|token_id}" argument if they also supply a target, which works but is kinda janky and puts more onus on the user.&nbsp; No jeez, keep on, please. :)
The Aaron and timmaugh Guys, this script you're talking about at the moment is a bit above me, I have not figured out this and that notation, yet, but is if for selecting numbers of tokens?&nbsp; I'm going to dig into it wither way.
1595983614

Edited 1595983686
timmaugh
Pro
API Scripter
Here it is with notation, Tim: // when your sandbox starts up, carve out a little space in the state object // we need it to exist before we can put things into it, // and creating it here is the easiest way to get it done on('ready', () =&gt; { &nbsp; &nbsp; state.message = {}; }); // this one listens for a chat event // remember that we're talking about this one being EARLIER in your script library // we want it to catch the message FIRST, before other scripts on('chat:message', (msg) =&gt; { &nbsp;&nbsp;&nbsp;&nbsp;// the next line makes sure that this is a call to the api &nbsp;&nbsp;&nbsp;&nbsp;// we don't test for the api handle (like: !tokenmod), because we want it to run for ALL messages &nbsp; &nbsp; if(msg.type === 'api') { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// messages generated from sendChat() come through with the playerid = 'API', so test for that &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// first we look for NOT api chats (so these were started by a player) &nbsp; &nbsp; &nbsp; &nbsp; if (msg.playerid !== 'API') { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// test for existence of a selected property to the message &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// if there is one, assign it to the state.message object we previously carved out &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(Object.prototype.hasOwnProperty.call(msg, "selected")) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.message.selected = msg.selected; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// this is a player message WITHOUT a selected property, so make the state match (delete it) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete state.message.selected; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } else { // handle API-generated messages &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// if we're in an API message, there won't be a selected object &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// but if we find one in state, grab it and add it to the message &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (state.message.selected) msg.selected = state.message.selected; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; } }); Since this would run before the other scripts, the next script in line (listening for its api handle) would actually be receiving a modified message object (now including the set of selected tokens from the state object). The idea of doing things this way would be to intercept the msg object so you could call someone else's script (like weaponarc) and feed it the appropriate data that you pull from a character sheet (construct the call). Since weaponarc works with the selected token, you have to pass that along, too. So your script would construct the call to weaponarc, knowing that the little scriptlet (above) would be delivering the state selected tokens just-in-time. The "that" and "callThat" were just little api listeners that demonstrated that the second script ("that") would indeed receive the state selected objects.
1595983884
timmaugh
Pro
API Scripter
I've also been pondering David's question given Aaron's good point. I can see an implmentation where the lead script (whose selected property gets peeled off by the all-listener scriptlet) actually constructs a button to run the second half of the script. Could be the same api listener, but the construction would handle the target token piece. You could code this up in your own script to make it work, easy enough. The more elegant (and more difficult trick) would be making it work for OTHER scripts the way the all-listener does for the selected tokens. That part I haven't worked out, yet. But I am ruminating.
1595986644
David M.
Pro
API Scripter
Timmaugh, here is a thread I started a few days ago about this exact issue. Aaron showed an example of what you are talking about, creating a chat button and storing selected ids from the parent api call. Then the button to prompts the target identification and "continues" the original command. It definitely works, just requires another click. I hadn't implemented it (yet) because I had already gotten something working with one click with my garbage spaghetti code and an extra argument, and have just been having more fun adding functionality :P I was curious if what you were talking about might be able to avoid the extra click with a recursive call, but seems the same limitation applies, as Aaron mentioned. <a href="https://app.roll20.net/forum/post/8998098/can-you-pass-both-a-selected-and-target-tokenid-to-an-api-script/?pageforid=8998098#post-8998098" rel="nofollow">https://app.roll20.net/forum/post/8998098/can-you-pass-both-a-selected-and-target-tokenid-to-an-api-script/?pageforid=8998098#post-8998098</a>