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

Sheetworker for repeating sums with multiple repeating sections?

1568593082
Kraynic
Pro
Sheet Author
I recently got thinking maybe I should add auto calculation of weight to my sheet.&nbsp; Not any encumbrance modifiers, just calculating and displaying total weight (total_weight).&nbsp; I have been looking at the wiki page that addresses that:&nbsp; <a href="https://wiki.roll20.net/RepeatingSum" rel="nofollow">https://wiki.roll20.net/RepeatingSum</a> Now, the snag is that inventory is broken up on my sheet.&nbsp; If I drop the "repeating_" off the sections, there is weapon, armor, gear, consumables, and wealth.&nbsp; I also realized when I got looking into this that only 2 sections currently have a place to enter weight (weapon and armor sections), and they are weapon_weight and armor_weight (after dropping the "attr_").&nbsp; I was planning to add a "section"_weight to each repeating section, but then I got looking at the example script, and wasn't sure if that would be the best way to go.&nbsp; Would I have to sum each repeating section individually and then add the 5 sections together to get a total?&nbsp; In that case, maybe I should have the last 3 have the same attribute for weight entry.&nbsp; I suppose if I need to do that, I would need at least a hidden total_weapon, total_armor, etc., and have a script watching for changes in the those to recalculate total weight.&nbsp; Is it possible to reference all 5 sections in the same script?&nbsp; If so, how would that be done, since I am pretty dense as far as figuring out scripts goes?
1568594610
GiGs
Pro
Sheet Author
API Scripter
The wiki says its a bad idea to have an attribute anywhere on the sheet named the same as one in a repeating section. In practice, I've done this and seen it done without issue, but it's probably a good idea not to rely on it. The simplest method would be to use repeatingSum, and add a worker for each repeating section which updates a hidden attribute. You then have one hidden attribute for each section. Finally, a worker watches those hidden attributes for changes, and sums them up and puts the total in a visible "encumbrance total" attribute. This is fairly undemanding - a change in one repeating section doesnt trigger a check of all 5 repeating sections. It just changes its one total. It adds 5 attributes to the sheet, but when talking about a sheet that already has 5 repeating sections with multiple attributes each, that's a miniscule addition. The alternative would be to have a custom function like repeatingSum which monitors all 5 repeating sections, and totals up and updates a single visible encumbrance total attribute. Aaron's TAS (The Aaron Sheet) might handle this, though the syntax is quite arcane to me.
1568594952
GiGs
Pro
Sheet Author
API Scripter
For reference, assuming you add the repeatingSum, these 2 workers would handle the calculation of armour and weapon weight, and would save them in hidden attributes named "encumbrance_weapon" and "encumbrance_armour"&nbsp; on('change:repeating_weapon remove:repeating_weapon', function() { repeatingSum("encumbrance_weapon","weapon","weapon_weight"); }); on('change:repeating_armour remove:repeating_armour', function() { repeatingSum("encumbrance_armour","armour","armour_weight"); }); You could add 3 more for the other sections, then create a worker with checks for changes in encumbrance_armour, encumbrance_weapon, encumbrance_gear, encumbrance_consumables, encumbrance_wealth and totals them up.
1568674382

Edited 1568674431
Kraynic
Pro
Sheet Author
GiGs said: This is fairly undemanding - a change in one repeating section doesnt trigger a check of all 5 repeating sections. It just changes its one total. It adds 5 attributes to the sheet, but when talking about a sheet that already has 5 repeating sections with multiple attributes each, that's a miniscule addition. Are repeating fields that big of a drain if they are just a few text/number inputs and a text area?&nbsp; I kept them in that form from the Palladium Megaverse sheet I started with.&nbsp; Why I thought it was good is that you could uncheck a category so that it won't show (not fill space) until you need to change something there.&nbsp; This would be the first script that interacts at all with any repeating section.&nbsp; Are repeating sections by themselves that big of a drain that they should be avoided?&nbsp; (Not that I will be doing anything about it now, but it would be something I would keep in mind for the future.) I'll fiddle with the scripts and see how badly I can mess things up before coming back for help.&nbsp;
1568675386
GiGs
Pro
Sheet Author
API Scripter
No, i dont think they are a big drain. I was just saying that in case you felt adding 5 attributes to calculate this would be too much, and just pointing out that relatively speaking, 5 attributes are smaller than any repeating section. There's absolutely nothing wrong with including repeating sections- they are a great feature, and many sheets would be way, way clunkier without them.
1568766142
Kraynic
Pro
Sheet Author
If I am looking at the right error output, do I need to be replacing all/some instances of "id" with something? ReferenceError: getweaponIDs is not defined destination: total_weapon (attr_total_weapon) section: weapon (repeating_weapon) fields: weapon_weight (attr_weapon_weight) What I have so far with those replacements: const repeatingSum = (total_weapon, weapon, weapon_weight, multiplier = 1) =&gt; { if (!Array.isArray(weapon_weight)) weapon_weight = [weapon_weight]; getweaponIDs(`repeating_${weapon}`, idArray =&gt; { const attrArray = idArray.reduce( (m,id) =&gt; [...m, ...(weapon_weight.map(field =&gt; `repeating_${weapon}_${id}_${field}`))],[]); getAttrs(attrArray, v =&gt; { console.log("===== values of v: "+ JSON.stringify(v) +" ====="); // getValue: if not a number, returns 1 if it is 'on' (checkbox), otherwise returns 0.. const getValue = (weapon, id,field) =&gt; parseFloat(v[`repeating_${weapon}_${id}_${field}`], 10) || (v[`repeating_${weapon}_${id}_${field}`] === 'on' ? 1 : 0); const sumTotal = idArray.reduce((total, id) =&gt; total + weapon_weight.reduce((subtotal,field) =&gt; subtotal * getValue(weapon, id,field),1),0); setAttrs({[total_weapon]: sumTotal * multiplier}); }); }); }; on('change:repeating_weapon remove:repeating_weapon', function() { repeatingSum("total_weapon","weapon","weapon_weight"); }); For now, I have a visible field on the sheet so I will be able to see if it is working.&nbsp; I am just doing this section for now, and once it is working I will do the rest.
1568768515

Edited 1568768568
GiGs
Pro
Sheet Author
API Scripter
Let me refer you to the text in the wiki page, just before the script: Include the following function - without changes - in the sheet worker script section of your character sheet. You just need one copy of repeatingSum, completely unaltered. Functions like this are meant to be reusable. You just supply some parameters, and it'll work. Look at the two examples I gave above: on('change:repeating_weapon remove:repeating_weapon', function() { repeatingSum("encumbrance_weapon","weapon","weapon_weight"); }); on('change:repeating_armour remove:repeating_armour', function() { repeatingSum("encumbrance_armour","armour","armour_weight"); }); Both of those workers call the same repeatingSum function. They just tell it the destination attribute, the repeating section to check, and the attribute in that section to sum up. In that order. So, given the fields you describe above: destination: total_weapon (attr_total_weapon) section: weapon (repeating_weapon) fields: weapon_weight (attr_weapon_weight) I think your function should be on('change:repeating_weapon remove:repeating_weapon', function() { repeatingSum("total_weapon","weapon","weapon_weight"); }); With an unaltered repeatingSum, that will look in the repeating_weapon section, sum up all weapon_weight rows, and save the sum on total_weapon.
1568772428
Kraynic
Pro
Sheet Author
I saw that, but then I took the explanations inside the code as directions. /* ===== PARAMETERS ========== destination = the name of the attribute that stores the total quantity section = name of repeating fieldset, without the repeating_ fields = the name of the attribute field to be summed can be a single attribute: 'weight' or an array of attributes: ['weight','number','equipped'] multiplier (optional) = a multiplier to the entire fieldset total. For instance, if summing coins of weight 0.02, might want to multiply the final total by 0.02. */ Seemed like it contradicted the other, so I went with what I thought this was telling me. So all I should need for totaling the different sections will be various versions of the 3 line function bit.&nbsp; I'll give it a go in a little bit.
1568776938
Kraynic
Pro
Sheet Author
Ok, I have it set up, and it is working.&nbsp; All it took was someone hitting me over the head with the obvious.&nbsp; I looked over some other scripts on my sheet and cobbled this up to add them together: on("change:total_weapon change:total_armor change:total_gear change:total_consumables change:total_wealth", function () { getAttrs(['total_weapon','total_armor','total_gear','total_consumables','total_wealth'], function (values) { const total_weapon = parseInt(values['total_weapon'])||0; const total_armor = parseInt(values['total_armor'])||0; const total_gear = parseInt(values['total_gear'])||0; const total_consumables = parseInt(values['total_consumables'])||0; const total_wealth = parseInt(values['total_wealth'])||0; const total_weight = (total_weapon+total_armor+total_gear+total_consumables+total_wealth); setAttrs({ total_weight: total_weight }); }); }); Is there a better (or more condensed) way to do this?&nbsp; It seems to work fine, but just thought I would ask.
1568779988

Edited 1568780641
GiGs
Pro
Sheet Author
API Scripter
Kraynic said: I saw that, but then I took the explanations inside the code as directions. /* ===== PARAMETERS ========== destination = the name of the attribute that stores the total quantity section = name of repeating fieldset, without the repeating_ fields = the name of the attribute field to be summed can be a single attribute: 'weight' or an array of attributes: ['weight','number','equipped'] multiplier (optional) = a multiplier to the entire fieldset total. For instance, if summing coins of weight 0.02, might want to multiply the final total by 0.02. */ Seemed like it contradicted the other, so I went with what I thought this was telling me. So all I should need for totaling the different sections will be various versions of the 3 line function bit.&nbsp; I'll give it a go in a little bit. That's explaining what the function's parameters are. It tells you what those variables in function are standing in for. So when the sumRepeating function is called, the destination &nbsp;variable you supply in your sheet worker gets substituted in everywhere the variable&nbsp; destination &nbsp;occurs in the sumRepeating function . The same happens with the other parameters there. Kraynic &nbsp;said: Ok, I have it set up, and it is working.&nbsp; All it took was someone hitting me over the head with the obvious.&nbsp; I looked over some other scripts on my sheet and cobbled this up to add them together: on("change:total_weapon change:total_armor change:total_gear change:total_consumables change:total_wealth", function () { getAttrs(['total_weapon','total_armor','total_gear','total_consumables','total_wealth'], function (values) { const total_weapon = parseInt(values['total_weapon'])||0; const total_armor = parseInt(values['total_armor'])||0; const total_gear = parseInt(values['total_gear'])||0; const total_consumables = parseInt(values['total_consumables'])||0; const total_wealth = parseInt(values['total_wealth'])||0; const total_weight = (total_weapon+total_armor+total_gear+total_consumables+total_wealth); setAttrs({ total_weight: total_weight }); }); }); Is there a better (or more condensed) way to do this?&nbsp; It seems to work fine, but just thought I would ask. You could compactify (new word) that by getting rid of the variable declarations, like so on("change:total_weapon change:total_armor change:total_gear change:total_consumables change:total_wealth", function () { getAttrs(['total_weapon','total_armor','total_gear','total_consumables','total_wealth'], function (values) { setAttrs({ total_weight: (parseInt(values['total_weapon'])||0) + (parseInt(values['total_armor'])||0) + (parseInt(values['total_gear'])||0) &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;+ (parseInt(values['total_consumables'])||0) + (parseInt(values['total_wealth'])||0) }); }); }); Personally I like to keep the variables declared, so I wouldn't - the way you've done it is the way I would do it. But there's nothing wrong with the approach above either. If you do go that way, the brackets around each term are important because of the || 0 part - you need each calculation isolated. parseInt function One technique you can do to make the code easier to write is to create a parseInt function to save you writing that out over and over. You could do this: on("change:total_weapon change:total_armor change:total_gear change:total_consumables change:total_wealth", function () { getAttrs(['total_weapon','total_armor','total_gear','total_consumables','total_wealth'], function (values) { const int = function(stat) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return parseInt(values[stat])||0; } const total_weapon = int('total_weapon'); const total_armor = int('total_armor'); const total_gear = int('total_gear'); const total_consumables = int('total_consumables'); const total_wealth = int('total_wealth'); const total_weight = total_weapon + total_armor + total_gear + total_consumables + total_wealth; setAttrs({ total_weight: total_weight }); }); }); Since you are doing the same operation over and over (parseInt, default 0), it's a perfect situation to create a reusable function. This function: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const int = function(stat) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return parseInt(values[stat])||0; } can be streamlined, using arrow functions, to: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const int = stat =&gt; parseInt(values[stat])||0; This is for all intents and purposes identical to the previous one. Now if you want to make a universal function like this that works for all sheet workers, you'll hit a snag, because it depends on the variable values . Values is not just a label, it's a variable which contains data, and e.g.&nbsp; values[ 'total_armor'] extracts a specific value from that variable. If you want to call this function in all sheet workers, you would need to also pass the values variable to it. You would do that like this: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const int = (stat, values) =&gt; parseInt(values[stat])||0; That's it, that's the only change you need to make. Then the sheet worker would look like this: &nbsp;&nbsp; const int = (stat, values) =&gt; parseInt(values[stat])||0; on("change:total_weapon change:total_armor change:total_gear change:total_consumables change:total_wealth", function () { getAttrs(['total_weapon','total_armor','total_gear','total_consumables','total_wealth'], function (values) { const total_weapon = int('total_weapon', values); const total_armor = int('total_armor', values); const total_gear = int('total_gear', values); const total_consumables = int('total_consumables', values); const total_wealth = int('total_wealth', values); const total_weight = total_weapon + total_armor + total_gear + total_consumables + total_wealth; setAttrs({ total_weight: total_weight }); }); }); Notice the change in the way the function is called, and the fact that the int function itself is outside the sheet worker. You should place it at the very start of your script block, then you can use it in all your sheet workers.
1568780893
GiGs
Pro
Sheet Author
API Scripter
Kraynic said: I saw that, but then I took the explanations inside the code as directions. /* ===== PARAMETERS ========== destination = the name of the attribute that stores the total quantity section = name of repeating fieldset, without the repeating_ fields = the name of the attribute field to be summed can be a single attribute: 'weight' or an array of attributes: ['weight','number','equipped'] multiplier (optional) = a multiplier to the entire fieldset total. For instance, if summing coins of weight 0.02, might want to multiply the final total by 0.02. */ Seemed like it contradicted the other, so I went with what I thought this was telling me. So all I should need for totaling the different sections will be various versions of the 3 line function bit.&nbsp; I'll give it a go in a little bit. Revisiting this - thanks for explaining your thought process there. You're not the first to try to edit the sumRepeating function, and it's helpful to know that the way I've presented it there might be misleading people. I'll think about how to rewrite the intro to it to help people avoid that in future.
1568833485
GiGs
Pro
Sheet Author
API Scripter
By the way, looking at your eqipment types, you might at some point want a more complex calculation. Let's say in your consumables, one item is healing potions. A healing potion weighs, say, 0.1, and a person carries 7 of them.&nbsp; So you could have inputs named consumables_weight , for the weight of an individual item, and consumables_count , for how many of that item you're crarying. The function for that would be on('change:repeating_weapon remove:repeating_weapon', function() { repeatingSum("total_consumables","consumables",["consumables_weight","consumables_count"]); });
1568834352
Kraynic
Pro
Sheet Author
Good point.&nbsp; I might want to do that with wealth stuff also.&nbsp; I tend to dump unique/rare non-coin treasures on my players, but that probably isn't the norm.&nbsp; Sometimes the worst thing about writing a sheet is trying to make it so other people can use it instead of just me.