Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Help - Display all skill-related perks from section when that skill is rolled

Like many games, Infinity grants characters unique talents that are relevant only for certain skills.  I'm trying to append a function to my dice rolling script that prints Talents below the roll results.   The sheet conveniently has fields in the Talents section that contain only the relevant skill - ergo there are 3 attributes related to each Talent: attr_ATallent_Name1, attr_ATallent_Skill1, attr_ATallentt_Effect1 And so on for each in the table.  Handily, one of the arguments I submit to the script should always equal the "attr_Atalent_Skill#".  !inf dice [[diceroll]] skillname @{selected|character_id} etc. parses in as  const args = msg.content.split(/\s+/);                          let c = getObj('character',args[3]);             // verify we got a character             if(c){                                  // get the character's focus attribute, or assume a focus of -1                 let name = getAttrByName(c.id, "character_name");                                  let npc = getAttrByName(c.id, "npc_options-flag");                                               const skill = args[2]; So theoretically, there should be a way to get all entries with ATalent_Skill attributes that match the skill constant, then output their ATalent_Name and ATalent_Effect. But I have no idea how to write that.  Any tips would be appreciated!
1558747783

Edited 1558747909
The Aaron
Roll20 Production Team
API Scripter
So, this is a little complicated. When you build a character sheet and want to have dynamic sections with rows you can add, that's called a Repeating Section . Repeating Sections are defined as follows: <fieldset class="repeating_skills"> That defines the "skills" Repeating Section.  Within the fieldset you define what each row should look like, effectively an HTML template for each row: <fieldset class="repeating_skills"> <div> <input type="text" name="attr_ATalent_Name1" /> <input type="number" name="attr_ATalent_Skill1" /> <input type="text" name="attr_ATalent_Effect1" /> <div> </fieldset> When the character sheet is being used, adding new rows creates a copy of that template, wrapped in a unique Row ID.  The values for that row are stored in specially named attributes that don't show up in the Attributes and Abilities tab.  They have the form: repeating_NAME_ROWID_ATTRIBUTE_NAME For example, the above might create: repeating_skills_-J7sa234safss_ATalent_Name1 repeating_skills_-J7sa234safss_ATalent_Skill1 repeating_skills_-J7sa234safss_ATalent_Effect1 With that out of the way, let's talk about how you find those. If you know the Name of the skill and the character id, it becomes a 3 step process: Find the repeating attribute named /^repeating_skills_[^_]+_ATalent_Name1/ which belongs to that character_id and has the skill name as its value. Build the names of the other skills you want to find based on the name of the one from step 1. Load all the other attributes by their names. For example: let nameAttr = findObjs({type:'attribute',characterid:args[3]}) .filter(a=> /^repeating_skills_[^_]+_ATalent_Name1$/.test(a.get('name')) && a.get('current') === args[2] )[0]; if(nameAttr){     let skillAttrName = nameAttr.get('name').replace(/Name1$/,'Skill1'); let effectAttrName = nameAttr.get('name').replace(/Name1$/,'Effect1'); let skillAttr=findObjs({type:'attribute',characterid:args[3],name:skillAttrName})[0]; let effectAttr=findObjs({type:'attribute',characterid:args[3],name:effectAttrName})[0]; /* do something with nameAttr, skillAttr, and effectAttr */ } Be sure to check that skillAttr and effectAttr exist before you use then, unless it's not possible for them to be missing if nameAttr is found.
1558763096

Edited 1558763157
Thanks Aaron!  Oh man, this is a brain melter.  Ok, so originally this part of the sheet wasn't repeating, but I went in and changed the table to be repeating so I could follow along, and it looks nicer.  I modified the Attribute names a little bit too since it comes with a default set of 2 rows (although - is this maybe why it isn't working yet?) The below code (pared down for easy testing) doesn't produce any output, though it's supposed to produce the variables you created.  Have I missed anything? on('ready', () => {     // register a function to run for chat messages     on('chat:message', (msg) => {         // make sure the message is an api command, then verify it starts with !infdice          if ('api' === msg.type && /^!talent\b/i.test(msg.content)) {             // break into an array of arguments on spaces             const args = msg.content.split(/\s+/);                          let c = getObj('character',args[2]);             // verify we got a character             if(c){                                  // get the character's focus attribute, or assume a focus of -1                 let name = getAttrByName(c.id, "character_name");                                  let npc = getAttrByName(c.id, "npc_options-flag");                                  let nameAttr = findObjs({type:'attribute',characterid:args[2]})   .filter(a=>   /^repeating_skills_[^_]+_ATalent_Name$/.test(a.get('name')) && a.get('current') === args[1]   )[0];             if(nameAttr){     let skillAttrName = nameAttr.get('name').replace(/Name$/,'Skill');     let effectAttrName = nameAttr.get('name').replace(/Name$/,'Effect');     let skillAttr=findObjs({type:'attribute',characterid:args[2],name:skillAttrName})[0];     let effectAttr=findObjs({type:'attribute',characterid:args[2],name:effectAttrName})[0];           sendChat('', `${skillAttr} ${effectAttr}`);            }          }         }     }); }); Macro is simply !talent Acrobatics @{selected|character_id} for now.   The character only has Acrobatics Talents, with all fields filled in.
1558799517
The Aaron
Roll20 Production Team
API Scripter
Now is when the debugging starts. =D You can use the log() function to dump out the contents of variables to the API Console.  You might start by verifying you found the character by adding this: log(c); right before the if(c) check.  If that outputs a character's data, then start checking to see that the filter function is working correctly.  Change it to this:   .filter(a=>{ log(a);   if(/^repeating_skills_[^_]+_ATalent_Name$/.test(a.get('name')) && a.get('current') === args[1]){     return true; }   })[0]; I'm going to guess that either the name or the current value are not matching. Go forth and liberally log() and understand what's going on!  =D One side note: sendChat('', `${skillAttr} ${effectAttr}`); If it does get to executing this line, the output will be: [object Object] [object Object] because those are both Javascript Objects, and so their toString() will just identify their type.  You would need to extract a value from them: sendChat('', `${skillAttr.get('current')} ${effectAttr.get('current')}`);
Thank you again ^_^ Wow, ok - that logs tons and tons of stuff. Is it supposed to log every single attribute on the sheet?  If so, yeah, that works.  If it's supposed to narrow it down right there then I don't think it's doing anything. How would I apply the log to the nameattr and other variables?
1558848622
The Aaron
Roll20 Production Team
API Scripter
Yeah, it would log all the attributes. A bit tedious, but you can then look through for the one you expect.  Log any attribute by passing it as an argument to log().
1558877531

Edited 1558885820
Ok, I tried logging this section and got NameAttr as undefined - which probably shouldn't be right, I suppose?  Did I use the log correctly, and is this the issue? let nameAttr = findObjs({type:'attribute',characterid:args[2]})    .filter(a=>{     if(/^repeating_talents_[^_]+_ATallent_Namerep1$/.test(a.get('name')) && a.get('current') === args[1]){       return true;     }   })[0];             log(nameAttr); Edit: note thay “aTallent_Namerep1” is not a typo; I had to alter the names of the attributes yet again to an earlier state; mucking around with the sheet caused me to lose the Talents I had added before...
1558885741
The Aaron
Roll20 Production Team
API Scripter
That's the correct way to do it.  So this tells you that it didn't find the attribute.  That means there are 3 possibilities: 1) It doesn't exist 2) The name of the attribute is different than expected 3) The current value of the attribute is not equal to args[1] Here's how you might verify those: let matchingNames = []; let matchingValues = []; let nameAttr = findObjs({type:'attribute',characterid:args[2]}) .filter(a=>{ if(/^repeating_talents_[^_]+_ATallent_Namerep1$/.test(a.get('name'))){ matchingNames.push(a.get('name')); } if(a.get('current') === args[1]){ matchingValues.push(a.get('name')); } if(/^repeating_talents_[^_]+_ATallent_Namerep1$/.test(a.get('name')) && a.get('current') === args[1]){ return true; } })[0]; log(`Attributes with matching name: ${matchingNames.join(', ')}`); log(`Attributes with matching value: ${matchingValues.join(', ')}`); The first bold block declares a couple of arrays to store the names in. The second bold block checks the individual qualities and adds them to the list the third bold block merges the values into a comma separated list and logs them out. One question, should it be "ATalent" instead of "ATallent"?
Hehe, sorry Aaron, you ninja'd me this morning.  While you were responding I was editing my post to say that "ATallent" is not a typo.  When I altered the attribute names sheet, all the Talents I had entered earlier were lost in the ether.  So I went back to the ugly clunky names so I could restore the original Talents.  The attribute names I'm posting now match the ones on the current sheet. I figured out some issues and I think I'm much closer to knowing what we need to fix.  I realized we had the order of this wrong.  It shouldn't be matching and replacing the "Name" attribute - it should be matching the "Skill."  So a Talent is listed out in the sheet as: Name: Graceful -- Skill: Acrobatics -- Effect: Reroll 1d20 on an Acrobatics test. Ergo, I wasn't getting anything because I was comparing "Acrobatics" with the Name, Graceful.  Time to change the code appropriately:                 let skillAttr = findObjs({type:'attribute',characterid:args[2]})    .filter(a=>{     if(/^repeating_talents_[^_]+_ATallent_Skillrep1$/.test(a.get('name')) && a.get('current') === args[1]){       return true;     }   })[0];             if(skillAttr){     let nameAttrName = skillAttr.get('name').replace(/Skill$/,'Name');     log(nameAttrName);     let effectAttrName = skillAttr.get('name').replace(/Skill$/,'Effect');     log(effectAttrName);     let nameAttr=findObjs({type:'attribute',characterid:args[2],name:nameAttrName})[0];     log(nameAttr);     let effectAttr=findObjs({type:'attribute',characterid:args[2],name:effectAttrName})[0];     log(effectAttr);           sendChat('', `${nameAttr.get('current')} ${effectAttr.get('current')}`);            }          }         }     }); }); Now that I've switched things up I'm much more on the right track.  There is now a chat output, but it is merely: "Acrobatics, Acrobatics." So it's going in circles.  I logged nameAttrName, effectAttrName, and so on, and the problem became fairly apparent: there is no replacement happening.  It just logs "Tallents_Skillrep1" as the attribute name for both, and "Acrobatics" as the current value. So: how can I get that replacement to work properly? 
1558905702
The Aaron
Roll20 Production Team
API Scripter
Ah!  Change this: /Skill$/ To this: /Skillrep1$/ and it should do the right substitution. $ means "end of line"
Ding-ding-ding, that was it. Yeah, I figured you could just replace it piecemeal, hence leaving off the rep1.  Whoops! Ok, so that perfectly outputs one Talent/ Effect pair for any Skill.  The log also grabs just the one Talent, so it's not leaving anything behind in the output.  How can I go further to make it fetch multiple Talent entries that use the same skill?
1558906747
The Aaron
Roll20 Production Team
API Scripter
if you take the [0] off the end of that .filter(), you'll get an array of all the matching attributes.  You can then iterate over that array and do the part you have above for each of them.  I'd probably suggest extracting what you have to be a GetOutputForAttribute() function, then you could call that function for each of the items in the iteration. Something like: const GetOutputForAttribute = (skillAttr)=>{ if(skillAttr){ let nameAttrName = skillAttr.get('name').replace(/Skillrep1$/,'Name'); log(nameAttrName); let effectAttrName = skillAttr.get('name').replace(/Skillrep1$/,'Effect'); log(effectAttrName); let nameAttr=findObjs({type:'attribute',characterid:args[2],name:nameAttrName})[0]; log(nameAttr); let effectAttr=findObjs({type:'attribute',characterid:args[2],name:effectAttrName})[0]; log(effectAttr); return `${nameAttr.get('current')} ${effectAttr.get('current')}`; } return ''; }; sendChat('',findObjs({type:'attribute',characterid:args[2]}) .filter(a=>{ if(/^repeating_talents_[^_]+_ATallent_Skillrep1$/.test(a.get('name')) && a.get('current') === args[1]){ return true; } }) .map(GetOutputForAttribute) .join('<br>') );
Fantastic!  That's it!  Thank you so much ^_^ If I may, I just wanted to try one more thing: What if I wanted to split the conditional output for each entry onto different lines of a template?  Like so: {{ [TalentName1]= [TalentEffect1] }} {{ [TalentName2]= [TalentEffect2] }}   And so on with all fetched Talents?
1558909704

Edited 1558909724
The Aaron
Roll20 Production Team
API Scripter
Something like this: const GetOutputForAttribute = (skillAttr)=>{ if(skillAttr){ let nameAttrName = skillAttr.get('name').replace(/Skillrep1$/,'Name'); log(nameAttrName); let effectAttrName = skillAttr.get('name').replace(/Skillrep1$/,'Effect'); log(effectAttrName); let nameAttr=findObjs({type:'attribute',characterid:args[2],name:nameAttrName})[0]; log(nameAttr); let effectAttr=findObjs({type:'attribute',characterid:args[2],name:effectAttrName})[0]; log(effectAttr); return ` {{ ${nameAttr.get('current')} = ${effectAttr.get('current')} }} `; } return ''; }; sendChat('', `&{template:default} ${findObjs({type:'attribute',characterid:args[2]}) .filter(a=>{ if(/^repeating_talents_[^_]+_ATallent_Skillrep1$/.test(a.get('name')) && a.get('current') === args[1]){ return true; } }) .map(GetOutputForAttribute) .join( '' ) }` );
Aaron... you're a god. ^_^