Using reduce there wasnt essential - any loop construction would have done, and are generally easier to figure out :) I use forEach a lot (as you can see earlier in the script), but could have easily used an old for loop, and that might better illustrate what's going on. I'll do that below, but first I'll give a crash course in sheetworkers, including why getSectionIDs is different. Sheet workers have no direct access to the character sheet - you can't modify it in any way. What you can do through the getAttrs function is read attribute values from the sheet, and with setAttrs, write values to the sheet. That's essentially all sheet workers can do. With getAttrs, you supply an array of attribute names, and it builds a variable (often called values by custom) that contains the attribute names and their values. It might look like either of these: getAttrs(['XP_total'], values => {
getAttrs(['XP_total'], function(values) { You then access them within your sheet worker using things like var XP_total = values.XP_total; or var XP_total = values['XP_total']; These are equivalent for most puposes -the first version is simpler to write, the second is needed in some cases (like when an attribute name contains characetrs that are illegal in javascript, like "-"). Since all roll20 attributes are stored as text, you often have to convert them into numbers (for instance, when you want to add a bunch of them up). There are several ways to do this. All of these work, and each has pros and cons, but mainly its a user preference. var str = parseInt(values.str) ||0; var str = +values.str || 0; var str = Number(values.str) ||0; The ||0 at the end of each is a logic function , and says "OR 0", which means if the attempt to convert the attribute to a number fails, you end with a value of 0. This helps avoid errors which break the sheet worker (like when you try to convert a word to a number). This is very important in roll20, when players might be entering the wrong values in textboxes. So we now know how to get attribute values, but attributes in repeating sections are special. You might give an attribute name advancement_costL , but then when you add new rows to the section, you end up with multiple attributes by that name. How can you access the correct one? To solve this problem, roll20 creates a composite name for every attribute in a repeating section. The names are made of three parts: the repeating fieldset name, an id for the row, and the name you created. They look like this: repeating_section _ id _ attribute name so your costL attribute name would actually look something like repeating_advancement_-hfgstr75hjyi_advancement_costL That is its true name, which can be accessed in macros and by getAttrs - the -hfgstr75hjyi part is the row id. They are generated randomly when the row is created, and are very complex to make sure that different rows in the same campaign wont have the same row id. Using getSectionIDs The getSectionIDs function gives you an array of all the row ids that exist , and you can then use it to construct the attributes names you need. So the procedure is: 1) use getSectionIDs to get a list of all the row ids 2) do a loop through the array, and build the names of all the attributes within the section you need 3) use getAttrs to retrieve their values (and any other attribute values you need) 4) now perform whatever operations you want to on those values (like adding together all the weights in an encumbrance section). You might have to loop through the ids array again, to do your processing. 5) and finally, use setAttrs to save the updated values to the sheet. The individual steps in the above are each fairly basic in terms of the levels of javascript skill you need to do them, once you know what you are doing, but they are really hard to figure out the first time you do each and there are so many different parts to learn it can be a real stumbling block. So here's an annotated version of the previous function, rewritten to be more beginner-friendly on('change:xp_total change:repeating_advancements:advancement_costL change:repeating_advancements:advancement_costR remove:repeating_advancements', () => { getSectionIDs(`repeating_advancements`, function(idArray) {
// create a variable to hold all the needed attribute names var fieldnames = [];
// loop throw the rowIDs and build their actual attributes names. adding them to the fieldnames array for(var i = 0; i < idArray.length; i++) { fieldnames.push( "repeating_advancements_" + idArray[i] + "_advancement_costL", "repeating_advancements_" + idArray[i] + "_advancement_costR" ); }
// if you need any other attributes in your worker, dont forget to include those fieldnames.push('XP_total'); // now use getAttrs to extract those attribute values from the sheet and store them in an object called values.
// since fieldnames is already an array, we dont need to use the usual [ ] brackets.
getAttrs(fieldnames, function(values) { // get the total XP vvalue var total = parseInt(values.XP_total) || 0; // create an attribute to hold the xp spent var spent = 0; // now loop through the row ids again, and grab their values, adding them to the spent total // the idArray variable created earlier by getSectionIDs still exists so we can use that. for(var i = 0; i < idArray.length; i++) {
// we likely need to build the attribute names again (there's a cleverer way, I'll mention later) var left = parseInt(values["repeating_advancements_" + idArray[i] + "_advancement_costL"]) ||0; // notice to get this we have to use the values['name'] version, not the values.name version // because we are building the names dynamically.
var right = parseInt(values["repeating_advancements_" + idArray[i] + "_advancement_costR"]) ||0; // now add those values to the ongoing spentXP total. spent = spent + left + right; } // now the loop is finished we have our total spent, and can output the attributes to the sheet. setAttrs({ XP_spent: spent, XP_current: total - spent }); }); }); }); I mentioned there's a cleverer way to do that for loop (but it's fairly advanced). First you need to understand the values object contains all the attributes we have defined, and only one of (XP_total) them in this function isnt part of the repeating section. So if we ignore that one, we can just sum up every value in the values object, and we dont need to know their names. // first, use destructuring to get all the values that are NOT XP_total const { XP_total, ...rest } = values; // now simply sum those values, without an explicit loop or needing to know their names. (You still need them for the getAttrs step though) const spent = Object.values(rest).reduce((sum, curr) => sum + curr, 0); But I'm just showing off here! I hope the previous example and explanation helped to explain what's going on.