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 required with Repeating Sections and API

November 01 (1 year ago)

I am trying to create a token action that will whisper a list of the character’s Talents to the GM

I can easily create a macro that calls the api script and passes the character_id to the api.

However, I am at a total loss on how to get information from a repeating row. Reading the various forum posts has left me feeling more confused than ever.

What I have is:

function tlntStr(cid){

       var character = getObj("character", cid);

       var retStr = "/w gm &{template:description}  {{item_name=" + getAttrByName(character.id, "character_name") + "}} {{name0=Talents }} {{stat0=";

       var tmpStr = "" ;

       // create an object/array to hold the repeating objects "repeating_talents"

       // for each rowid in the above return attr_talentname and add to tmpStr

       retStr += tmpStr +"}}"

       return retStr  ;

};

 

Do I use getAttrByName; or filterObj on the character obj created; or GetObj to return the repeating section?

Any advice would be appreciated.

 

November 01 (1 year ago)
The Aaron
Roll20 Production Team
API Scripter

Repeating section fields are attributes with a particular naming convention.  They are otherwise like other attributes. To get them for a character, you need to use findObjs() (or filterObjs()). 

The naming convention is:

repeating_SECTIONNAME_ROWID_FIELDNAME

For example:

repeating_talents_-hjk68hj654_talentname

The row id is not predictable, but all the repeating attributes with the same row id are in the same row:

repeating_talents_-hjk68hj654_talentcost

You can find all the attributes for a given character, then filter them based on the name to get the attributes in a repeating section, then group those on the row id to get all the fields for a row together.


November 01 (1 year ago)

Edited November 01 (1 year ago)
timmaugh
Forum Champion
API Scripter

To extend on what Aaron said, here is more info on repeating attributes.

Here is a function to get the repeating attributes for a character (by character id). If you know the list/section (i.e., "talent"), you can supply that to limit the returned attributes:

    const getRepeatingForCharacter = (charid, list) => {
        if (!charid) { return []; }
        return findObjs({ type: 'attribute', characterid = charid })
            .filter(c => {
                return list
                ? /^repeating_([^_]*?)_([^_]*?)_(.+)$/i
                : new RegExp(`^repeating_${list}_([^_]*?)_(.+)$`,'i')).test(c.get('name')));
            };
    };

Note that this will have all of the sub-attributes for each entry. If you're just going to want the names of the talents, you'll only want one sub-attribute (for instance, "talentname"). That attribute will still have a name like what Aaron described: 

repeating_talent_-Mabcdef1234567890_talentname

In which case you can filter your results by another regex that tests whether the attribute name ends with "talentname", then map your results to the contents of the attribute (the "current" value):

let theBestCharInTheWholeWideWorld = getTheChar(); // code to get your character object
let charTalents = getRepeatingForCharacter(theBestCharInTheWholeWideWorld.id, 'talent')
    .filter(a => /*_talentname$/i.test(a.get('name')))
    .map(a => a.get('current'));

(feel free to add a .sort() to the end of that chain of operations if it is important to you to have the returned set be alphabetized.)

If you were going to feed charTalents (an array) to an output like a default roll template, you might change that last mapping to be something more like:

    .map((a,i) => `{{Talent ${i + 1} =${a.get('current')}}}`);

(yes, I made that 1-based... you can remove the +1 to get it to be 0-based if you want)

Then you could just append it to a default template statement:

    let msg = `${template:default} ${charTalents}`;

...and send that to the GM.

Alternatively, you might want to have not only the name of the talent in your list, but also a description field, as well. The description would be another sub-attribute attached to the entry in the talent list:

repeating_talent_-Mabcdef1234567890_description

...and would be in the array of attributes returned by the getRepeatingForCharacter() function, above. The trick is to not only filter your array of attributes to only the talentname and description attributes, but to make sure that you are connecting the right description to a given talentname. To do that, you'd use the rowid (refer to Aaron's breakdown of the naming convention, above).

If I had to do this, I would build a data structure to hold what I needed. I'd either get a unique set of rowids from the attributes which I would then use as properties on a new object, or I'd use a reduce() against my array and build the same data structure. Either way, I'd get an object that looked like:

{
  (rowid1): {
    talentname: (talentname1),
description: (description1) }, (rowid2): { talentname: (talentname2), description: (description2) }, //...etc. }

You can get the unique rowids from your array using this function:

    const getAllRepIDs = (r) => {
        return [...new Set([...r.map(c => /^repeating_[^_]*?_([^_]*?)_.+$/i.exec(c.get('name'))[1])])];
    };

Though now that I think of it, I think it would be less efficient to use this function to get your keys to an object, then go about testing each of your attributes to see if it needs to be attached as the "talentname" or "description" property of that key... you might end up testing attributes multiple times.

Better to go through a reduction of the original array of attributes and build the object along the way, testing each attribute only once:

let theBestCharInTheWholeWideWorld = getTheChar(); // code to get your character object
let charTalents = getRepeatingForCharacter(theBestCharInTheWholeWideWorld.id, 'talent')
    .filter(a => /*_(talentname|description)$/i.test(a.get('name'))) .reduce((m,a) => { let attrres = /^repeating_[^_]*?_([^_]*?)_.+$/i.exec(c.get('name')); let [rowid,name] = [attrres[1], attrres[2]];
m[rowid] = m[rowid] || {}; m[rowid][name] = a.get('current'); return m; },{});

(above is air-coded, so test it first...)

Once that object is built, you can construct your output from the keys:

let templateParts = Object.keys(charTalents).map(k => {
  return `{{${charTalents[k].talentname}=${charTalents[k].description}}}`;
});
let msg = `${template:default} ${templateParts.join('')}`;

Again, that is geared toward sending the default template; you can adjust as you see fit. One final thing to be aware of, if you go a route like this, is that you might need to sanitize the description field so it doesn't break your HTML output... but that's a discussion for another day.

November 02 (1 year ago)

Many thanks to The Aaron and timmaugh for their pointers.

I have managed to achieve what i wanted, the code may not be pretty, but it does what i want it to do. All that remains to to tidy up the output string for use with a template to make it look pretty inteh chat window.

function tlntStr(cid){
    var character = getObj("character", cid);
    getAttributes = (cid) => {
        return findObjs({
            _type: 'attribute',
            _characterid: cid
        });
    };
    
    getRepeatingIDs = (cid, section, attribute) => {
        const repeating = getAttributes(cid)
            .filter((obj) => {
                return obj.get('name').startsWith(`repeating_${section}_`) && obj.get('name').endsWith(attribute);
            })
            .map(obj => obj.get('name').replace(`repeating_${section}_`, '').replace('_' + attribute, '').trim());
        return repeating;
    };
    var myTlnts = getRepeatingIDs(character.id, "talents", "talentname");
    var tlntStr=""
    for (j=0; j < myTlnts.length; j++) {
        tlntStr += getAttrByName(character.id, "repeating_talents_" + myTlnts[j] + "_talentname") + "\n";
    };
    var retStr = "returning:\n" + tlntStr;  
    
    return retStr  ;
};