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

[idea] repeating section -> token action

So with the changes to the repeating sections in character sheets and the fact that there is now a unique ID set to each of the abilities it is a bit more tedious to make token actions for these.  Yes you can use the "$#" reference for them which help, but I was thinking that it would be helpful to have a script search through a specific repeating section, grab the name (or another field) and use it to automatically create the token action. I think this is possible and I wouldn't mind exploring it, but if someone could confirm it is possible, and potentially put some basic shoestring code together I might be able to take it from there. Seems there are a number of requests for this and it could help less technical players out.
1454523057
The Aaron
Pro
API Scripter
Should be possible.  I've been meaning to explore this. 
The Aaron said: Should be possible.  I've been meaning to explore this.  I think at that point, we have to buy you a cape and tights. I don't even want to THINK about how much time I just spent doing this for the characters in my new campaign.
1454527598
The Aaron
Pro
API Scripter
David, are you looking through my web cam?  =D
I didn't know it was available... We may need a new site. :P
1454530750
The Aaron
Pro
API Scripter
=D  
Aaron, So I am starting to take a stab at this, but I am trying to use your scripts as a bit of a guide for how to do things properly.  So looking at your scripts I see that you often setup the following (using the Ammo script as an example, but almost all of your scripts are setup in a similar fasion): on("ready",function(){ 'use strict'; Ammo.CheckInstall(); Ammo.RegisterEventHandlers(); }); So as I understand it, what you are doing here is setting up the script to run once the sandbox is up and ready.  Then you have event handlers that will process things like capturing the api commands, or handling graphics changes, which if I am correct makes sense. There is one section though that I am not understanding (again using a fragment of the Ammo script): RegisterEventHandlers = function() { on('chat:message', HandleInput); }; return {         CheckInstall: checkInstall, RegisterEventHandlers: RegisterEventHandlers }; I understand that RegisterEventHandlers is function that runs when a new chat message event occurs and that HandleInput is the actual processing of that event.  What I don't understand is what "RegisterEventHandlers: RegisterEventHandlers" is actually doing. If I am way off and asking naive questions I apologize up front... I am not a coder who works with coders all day, albeit very different coders (kernel level C and Assembly), so I am trying to learn.  I have "the book" but it feels a bit over my understanding at this stage, so hoping it will be something I can build up to fully grasp.
I hate to ask this, but can you provide a simplified event handler example.  I have tried to isolate this in your code and I am struggling to understand it and can't seem to get anything working.
1454609981
The Aaron
Pro
API Scripter
C and assembly! &nbsp;Now you're speaking my language. =D You are almost entirely correct about the code above, what you're missing is that RegisterEventHandlers() is only executed once to setup the events. &nbsp;The function HandleInput() is what gets invoked for each chat message. In javascript, you don't (previous to ES6) have an explicit separation between public and private variables (objects/ functions /arrays/strings/etc). &nbsp;To get around this, you can create a things in a function (which has it's own scope) and return them out of that scope (causing the scope to persist, a closure). &nbsp;Things created in the inner function scope (the closure) have access to the other things in that scope. Here's a bit of pseudo code that might help: // create module object var pubObj = (function(){ // setup private things var privateFunc1 = function(){ /* do stuff */ }, privateFunc2 = function(){ /* do other stuff */ }, privateFunc3 = function(){ /* do stuff and call function in closure */ privateFunc2(); }; // return the object to assign to pubObj return { publicAliasFunc1: privateFunc1, publicAliasFunc2: privateFunc3 //&lt;-- The names don't matter, 2 and 3 here are intentional }; }()); on('ready',function(){ pubObj.publicAliasFunc1(); pubObj.publicAliasFunc2(); }); pubObj gets assigned the object literal created at the return statement, which has 2 properties which happen to be functions. &nbsp;Those properties are assigned the function in the closure, bringing them out so they can be executed lower down in the on('ready',...) event. &nbsp;You can see that privateFunc3() calls privateFunc2() which is still inside the closure. &nbsp;It has access to that function because it was created in that scope. That should arm you with the tools to understand what's going on in my scripts. Definitely ask if you want more details! Simplified event handler examples (Same event, multiple handlings.): on('ready',function(){ log('ready fired.'); }); // -----------------ALTERNATIVE-------------------- var handleReady = function(){ log('ready fired.'); }; on('ready',handleReady); // -----------------ALTERNATIVE-------------------- var readyObj = (function(){ var internalHandleReady = function(){ log('ready fired.'); }; return { readyHandler: internalHandleReady }; }()); // either: on('ready',readyObj.readyHandler); // or on('ready',function(){ readyObj.readyHandler(); }); Here's one for chat messages: var chatObj = (function(){ var handleInput = function(msg){ log('Got msg: '+msg.content); }, registerEventHandlers(){ on('chat:message',handleInput); }; return { RegisterEventHandlers: registerEventHandlers }; }()); on('ready',function(){ chatObj.RegisterEventHandlers(); }); My having a registerEventHandlers() is largely a organizational convenience. &nbsp;I use the following template to create all my scripts, with a bash script that subs for the NAME part: // Github: &nbsp; <a href="https://github.com/shdwjk/Roll20API/blob/master/NAME/NAME.js" rel="nofollow">https://github.com/shdwjk/Roll20API/blob/master/NAME/NAME.js</a> // By: &nbsp; &nbsp; &nbsp; The Aaron, Arcane Scriptomancer // Contact: &nbsp;<a href="https://app.roll20.net/users/104025/the-aaron" rel="nofollow">https://app.roll20.net/users/104025/the-aaron</a> var NAME = NAME || (function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp; lastUpdate = 1427601167, &nbsp; &nbsp; &nbsp; &nbsp; schemaVersion = 0.1, &nbsp; &nbsp; checkInstall = function() { log('-=&gt; NAME v'+version+' &lt;=- &nbsp;['+(new Date(lastUpdate*1000))+']'); &nbsp; &nbsp; &nbsp; &nbsp; if( ! _.has(state,'NAME') || state.NAME.version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(' &nbsp;&gt; Updating Schema to v'+schemaVersion+' &lt;'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.NAME = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; version: schemaVersion &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; handleInput = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var args; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!NAME': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; registerEventHandlers = function() { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; CheckInstall: checkInstall, &nbsp; &nbsp; &nbsp; &nbsp; RegisterEventHandlers: registerEventHandlers &nbsp; &nbsp; }; }()); on('ready',function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; NAME.CheckInstall(); &nbsp; &nbsp; NAME.RegisterEventHandlers(); }); &nbsp;Let me know if you want more! &nbsp;=D
This is super helpful, thank you! &nbsp;I am slowly starting to work my way through this getting the bits and pieces together. &nbsp; One thing that is strange in, in the last example above instead of "checkInstall = function() {", I needed to do a "var&nbsp;checkInstall = function() {".
Ok, so I am running into issues now trying to pull up the repeating fields. &nbsp;After getting the character.id from the selected token, I am simply trying to get the name of the repeating attack using a simple getAttrByName: var repeatingAttack = getAttrByName(character.id, "repeating_attack_$1_atkname_base"); This is using the 5eSRD sheet, with a linked token that has this field populated. &nbsp;In fact when I run an "@{characterName|repeating_attack_$1_atkname_base} in chat I do get the corresponding value back.&nbsp; The API console shows the following: "Error: No attribute or sheet field found for character_id -K9OSNQug3FV-2U-_-xL named repeating_attack_$1_atkname_base" Not sure if I am missing something or approaching this incorrectly... &nbsp;This is on PROD, if it matters.
Here is the script I have so far, I have some ideas on how to loop through the repeating section to get the names and to create the abilities, but without being able to get the repeating attributes the point is pretty moot. var tokenAction = tokenAction || (function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var version = '0.1.0'; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; var checkInstall = function() { &nbsp; &nbsp; &nbsp; &nbsp; log('TokenAction v'+version+' is ready!'); &nbsp; &nbsp; }, &nbsp; &nbsp; handleInput = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var args; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!ta': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.selected && msg.selected.length) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var token = getObj('graphic', msg.selected[0]._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //log (token); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (token) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var character = getObj('character', token.get('represents')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingAttack = getAttrByName(character.id, "repeating_attack_$0_atkname_base"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(repeatingAttack); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //log(msg); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "No token selected"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //log("break"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; registerEventHandlers = function() { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; CheckInstall: checkInstall, &nbsp; &nbsp; &nbsp; &nbsp; RegisterEventHandlers: registerEventHandlers &nbsp; &nbsp; }; }()); on('ready',function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; tokenAction.CheckInstall(); &nbsp; &nbsp; tokenAction.RegisterEventHandlers(); });
1454638517
The Aaron
Pro
API Scripter
Kevin said: One thing that is strange in, in the last example above instead of "checkInstall = function() {", I needed to do a "var&nbsp;checkInstall = function() {". It's because you have a ; after your version and not a , Kevin said: Ok, so I am running into issues now trying to pull up the repeating fields. &nbsp;After getting the character.id from the selected token, I am simply trying to get the name of the repeating attack using a simple getAttrByName: var repeatingAttack = getAttrByName(character.id, "repeating_attack_$1_atkname_base"); I brought this up to Riley. &nbsp;He said it will work with sendChat() to expand the attribute. &nbsp;He's going to look into getting it working with getAttrByName().
Thank you Aaron noting the ; vs. , that was driving me a bit nuts and knew I had to be missing something simple. Also, thanks for bringing to the attention of Riley. I looked through the API documentation, is there a way to get a list of repeating attributes associated with a character without providing the specific name, but instead a naming pattern? &nbsp;Maybe findObj can do this? &nbsp; Just trying to look at different ways to create a list of attributes that exist, grab the id from it, and then create the new ability. Thanks again for your patience and assistance.
1454696420
The Aaron
Pro
API Scripter
This should give you all the attributes for a character that are repeating attributes: var repeatingAttrs = filterObjs(function(o){ return o.get('type')==='attribute' && o.get('characterid') === character.id && o.get('name').match(/repeating_/); }); I'm both home sick and working on an emergency fix to a site at work, or I'd give you better details. =/ &nbsp; I'll try to revisit this later tonight.
1454703939

Edited 1454703987
vÍnce
Pro
Sheet Author
Having just gotten over an ear infection... &nbsp;Get well soon. &nbsp;;-) Back on topic. Would this script automate token/ability macros from repeating sections or is this just part of something bigger?
That is the goal Vince, in initially I suspect it would need to be made per sheet, but once the framework it established I am hoping it will be easy to port to other sheets.
1454796538

Edited 1454796813
Ok, so I have a script more or less working for the SRD5e sheet, there are some limitations though. &nbsp; It is currently setup to only create an ability macro for the "full" roll, meaning it will still roll damage when "Don't Auto Roll Damage" is set. If you run the command multiple time it will create multiple macros (probably a solution to this, but will likely need help figuring it out). However I feel like there is a bit of ugly coding in the middle where I created a 'for' loop to accomplish things, so looking for recommendations on how to improve it. &nbsp;I think that this can be done using _.each, however I played around with it for a few hours and couldn't figure it out. &nbsp; Anyway, if anyone is interested, the script is below, you simply select a token that represents a character that has attacks listed that you want to create token action ability macros for and run the api command '!ta': var tokenAction = tokenAction || (function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; checkInstall = function() { &nbsp; &nbsp; log('TokenAction v'+version+' is ready!'); &nbsp; &nbsp; }, &nbsp; &nbsp; handleInput = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var args; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!ta': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.selected && msg.selected.length) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var token = getObj('graphic', msg.selected[0]._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (token) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var character = getObj('character', token.get('represents')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var characterName = character.get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingAttrs = filterObjs(function(o){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return o.get('type')==='attribute' && o.get('characterid') === character.id && o.get('name').match(/repeating_attack_.*_atkname_base/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var a; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (a = 0; a &lt; repeatingAttrs.length; a++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingId = repeatingAttrs[a].get('name').split('_')[2]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingCurrent = repeatingAttrs[a].get('current'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingAction = "%{" + characterName + "|repeating_attack_" + repeatingId + "_attack_full}"; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createObj("ability", { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name: repeatingCurrent, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; action: repeatingAction, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; characterid: character.id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "No token selected"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; registerEventHandlers = function() { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; CheckInstall: checkInstall, &nbsp; &nbsp; &nbsp; &nbsp; RegisterEventHandlers: registerEventHandlers &nbsp; &nbsp; }; }()); on('ready',function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; tokenAction.CheckInstall(); &nbsp; &nbsp; tokenAction.RegisterEventHandlers(); });
The Aaron said: This should give you all the attributes for a character that are repeating attributes: var repeatingAttrs = filterObjs(function(o){ return o.get('type')==='attribute' && o.get('characterid') === character.id && o.get('name').match(/repeating_/); }); I'm both home sick and working on an emergency fix to a site at work, or I'd give you better details. =/ &nbsp; I'll try to revisit this later tonight. This worked great btw... I think I understand how it works, just need to get better at RegEx as well. &nbsp;Hope that you are feeling better today!
So I cleaned this up a little bit by eliminating the creation of duplicate abilities, still have a bit of an ugly for loop in there that I am sure could be improved, but up for suggestions on improving it: var tokenAction = tokenAction || (function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; checkInstall = function() { &nbsp; &nbsp; log('TokenAction v'+version+' is ready!'); &nbsp; &nbsp; }, &nbsp; &nbsp; handleInput = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var args; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!ta': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.selected && msg.selected.length) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var token = getObj('graphic', msg.selected[0]._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (token) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var character = getObj('character', token.get('represents')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var characterName = character.get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingAttrs = filterObjs(function(o){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return o.get('type')==='attribute' && o.get('characterid') === character.id && o.get('name').match(/repeating_attack_.*_atkname_base/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var a; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (a = 0; a &lt; repeatingAttrs.length; a++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingId = repeatingAttrs[a].get('name').split('_')[2]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingName = repeatingAttrs[a].get('current'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingAction = "%{" + characterName + "|repeating_attack_" + repeatingId + "_attack_full}"; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var checkAbility = findObjs({_type: 'ability', _characterid: character.id, name: repeatingName}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (checkAbility[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; checkAbility[0].set({action: repeatingAction}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w GM Updated token action " + repeatingName + " on " + characterName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createObj("ability", {name: repeatingName, action: repeatingAction, characterid: character.id}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w GM Created token action " + repeatingName + " on " + characterName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w GM No token selected"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; registerEventHandlers = function() { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; CheckInstall: checkInstall, &nbsp; &nbsp; &nbsp; &nbsp; RegisterEventHandlers: registerEventHandlers &nbsp; &nbsp; }; }()); on('ready',function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; tokenAction.CheckInstall(); &nbsp; &nbsp; tokenAction.RegisterEventHandlers(); });
1454876426
The Aaron
Pro
API Scripter
That looks pretty good! &nbsp;Here are some minor changes. &nbsp;JSLint warnings cleared up for putting var statements together, changed out your for for _.each(), minor (pedantic) change to your regex: var tokenAction = tokenAction || (function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; checkInstall = function() { &nbsp; &nbsp; log('TokenAction v'+version+' is ready!'); &nbsp; &nbsp; }, &nbsp; &nbsp; handleInput = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var args,token,character,characterName,repeatingAttrs; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!ta': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.selected && msg.selected.length) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; token = getObj('graphic', msg.selected[0]._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (token) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; character = getObj('character', token.get('represents')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; characterName = character.get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingAttrs = filterObjs(function(o){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return o.get('type')==='attribute' && o.get('characterid') === character.id && o.get('name').match(/repeating_attack_[^_]+_atkname_base/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.each(repeatingAttrs,function(attr){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingId = attr.get('name').split('_')[2], &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingName = attr.get('current'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingAction = "%{" + characterName + "|repeating_attack_" + repeatingId + "_attack_full}", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; checkAbility = findObjs({_type: 'ability', _characterid: character.id, name: repeatingName}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (checkAbility[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; checkAbility[0].set({action: repeatingAction}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w GM Updated token action " + repeatingName + " on " + characterName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createObj("ability", {name: repeatingName, action: repeatingAction, characterid: character.id}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w GM Created token action " + repeatingName + " on " + characterName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w GM No token selected"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; registerEventHandlers = function() { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; CheckInstall: checkInstall, &nbsp; &nbsp; &nbsp; &nbsp; RegisterEventHandlers: registerEventHandlers &nbsp; &nbsp; }; }()); on('ready',function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; tokenAction.CheckInstall(); &nbsp; &nbsp; tokenAction.RegisterEventHandlers(); }); Of course, I didn't try running it... =D
Thanks Aaron! &nbsp;So I made the suggested changes, still processing the _.each to make sure I get it. &nbsp;Can you explain the RegEx change a bit, RegEx can be confusing. &nbsp;Also made an additional check in the event that a token is selected, but doesn't represent anything. var tokenAction = tokenAction || (function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var version = '0.1.0', &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; checkInstall = function() { &nbsp; &nbsp; &nbsp; &nbsp; log('TokenAction v'+version+' is ready!'); &nbsp; &nbsp; }, &nbsp; &nbsp; handleInput = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var args,token,character,characterName,repeatingAttrs; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!ta': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (msg.selected && msg.selected.length) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; token = getObj('graphic', msg.selected[0]._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (token.get('represents')) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; character = getObj('character', token.get('represents')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; characterName = character.get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingAttrs = filterObjs(function(o){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return o.get('type')==='attribute' && o.get('characterid') === character.id && o.get('name').match(/repeating_attack_[^_]+_atkname_base/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.each(repeatingAttrs,function(attr){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var repeatingId = attr.get('name').split('_')[2], &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingName = attr.get('current'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; repeatingAction = "%{" + characterName + "|repeating_attack_" + repeatingId + "_attack_full}", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; checkAbility = findObjs({_type: 'ability', _characterid: character.id, name: repeatingName}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (checkAbility[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; checkAbility[0].set({action: repeatingAction}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w " + msg.who + " Updated token action " + repeatingName + " on " + characterName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; createObj("ability", {name: repeatingName, action: repeatingAction, characterid: character.id}); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w " + msg.who + " Created token action " + repeatingName + " on " + characterName); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w " + msg.who + " Token does not represent a player"); } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("TokenAction", "/w " + msg.who + " No token selected"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }, &nbsp; &nbsp; registerEventHandlers = function() { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; CheckInstall: checkInstall, &nbsp; &nbsp; &nbsp; &nbsp; RegisterEventHandlers: registerEventHandlers &nbsp; &nbsp; }; }()); on('ready',function() { &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; tokenAction.CheckInstall(); &nbsp; &nbsp; tokenAction.RegisterEventHandlers(); });
1454898885

Edited 1454899076
The Aaron
Pro
API Scripter
Sure! &nbsp;You had .* in the middle, which matches 0 or more of anything ( . === anything, * === 0 or more of the preceding matcher). I changed it to [^_]+ which is 1 or more of anything that isn't an _ ( [ ] delimit characters to match, ^ inverts the character list, _ matches a literal _, so [^_] is all the characters that aren't _. &nbsp;+ === 1 or more of the previous matcher. ). It's a pedantic change as in this case both would likely produce the same subset of matches, but JSLint whines about . in regexes. :). Better might be [-a-zA-Z0-9]+ but who wants to be THAT pedantic! &nbsp;:) _.each() is super useful once you grok it. Rather than explain it here though, I'll point you to where I did a write up in the wiki:&nbsp; <a href="https://wiki.roll20.net/API:Cookbook#Calling_a_fun" rel="nofollow">https://wiki.roll20.net/API:Cookbook#Calling_a_fun</a>... What else can I help with? &nbsp;:)
Thank you very much! &nbsp;The link describing _.each was very helpful, makes much more sense now. &nbsp;I hope to do a few more tests, then I may try to release it, though not how to release it in the repository, need to read more about that.