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 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 => g.get("top") < 200 && g.get("left") < 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 => [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 = []; // 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; // 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] ||
findObjs({ type: 'character' }).filter((chr) => { return chr.get('name') === s; })[0] || findObjs({ type: 'character', id: (getObj("graphic", s) || { get: () => { 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) => { // ...}); 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 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 = ({
m: m, // msg object d: d = " " // delimiter (default is space) } = {}) => { return m.selected.reduce((a, v) => { return a + v.id + d; }, "").trim(); }; 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 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 API:Object 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) => { let attrDefaultTable = {
"Strength": 1,
"Mojo": 0,
"DanceMoxie": 10
} let defvalue = attrDefaultTable[attr]; let retAttr = findObjs({ type: 'attribute', characterid: charid, name: attr })[0] ||
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 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
.filter( t => return true if the associated character sheet shows a type of artillery OR mortar )
.forEach( manipulate the aura ) So... air-coding and UNTESTED... here's how you filter for mortars and artillery... let c;
let batteries = msg.selected .filter( t = > {
c = findObjs({ type: 'character', id: t.get("represents") })[0] || { id: "" };
return ['mor', 'arty'].includes(getAttrByName(c.id, 'Unit_Type', 'current'));
});
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 = > ['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 & 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!