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.