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 Case Statement Confusion

I'm very JS-ignorant and have limited experience with sheetworkers, and I wonder if anyone can help me spot what I'm doing wrong. I'm using the following sheetworker in a custom White Wolf Aberrant sheet of mine. It basically works; however, the nested case statement is returning the default value no matter what I enter. "SpecAttrName" is the name of the attribute being called (such as "strength") Value 1: "strength" and etc. are the value of a raw attribute (0, 1, 2, etc.) Value 2: "m_strength" and etc. are the value of a raw "mega" attribute (0, 1, 2, etc.) Value 3: "m_strength_mod" and etc. are calculated values based on the corresponding "mega" attributes ("m_strength," etc.); due to the formula I have to use to calculate these, the "mod" value defaults to a minimum of 1, so I need to check to see if the base mega value is 0; if it's not 0, I need to use the calculated value, and otherwise I need to use 0. Based on the name of the attribute entered, the worker correctly pulls the value for the raw attribute ("strength," etc.). It can pull the calculated values as well ("m_strength_mod," etc.), but it returns the calculated mod value for the mega attribute even if the mega attribute is 0. Basically, I just need to be able to check if value 2 is 0. If it's 0, I need to return 0. If it's <0, I need to return value 3. I hope that makes sense. However, it always returns value 3, no matter what the input is, and never returns 0. Can anyone help me spot what I'm doing wrong here? on("change:repeating_specials:SpecAttrName", function() { getAttrs(["repeating_specials_SpecAttrName","strength","m_strength","m_strength_mod","charisma","m_charisma","m_charisma_mod","intelligence","m_intelligence","m_intelligence_mod","dexterity","m_dexterity","m_dexterity_mod","manipulation","m_manipulation","m_manipulation_mod","wits","m_wits","m_wits_mod","stamina","m_stamina","m_stamina_mod","composure","m_composure","m_composure_mod","resolve","m_resolve","m_resolve_mod"], function(values) { let mySpAttrName = values.repeating_specials_SpecAttrName; let mystrength = values.m_strength; let mycharisma = values.m_charisma; let myintelligence = values.m_intelligence; let mydexterity = values.m_dexterity; let mymanipulation = values.m_manipulation; let mywits = values.m_wits; let mystamina = values.m_stamina; let mycomposure = values.m_composure; let myresolve = values.m_resolve; let mySpAttr = 0; let mySpEAttr = 0; switch (mySpAttrName) { case "strength": mySpAttr = values.strength; switch (mystrength) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_strength_mod; } break; case "charisma": mySpAttr = values.charisma; switch (mycharisma) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_charisma_mod; } break; case "intelligence": mySpAttr = values.intelligence; switch (myintelligence) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_intelligence_mod; } break; case "dexterity": mySpAttr = values.dexterity; switch (mydexterity) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_dexterity_mod; } break; case "manipulation": mySpAttr = values.manipulation; switch (mymanipulation) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_manipulation_mod; } break; case "wits": mySpAttr = values.wits; switch (mywits) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_wits_mod; } break; case "stamina": mySpAttr = values.stamina; switch (mystamina) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_stamina_mod; } break; case "composure": mySpAttr = values.composure; switch (mycomposure) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_composure_mod; } break; case "resolve": mySpAttr = values.resolve; switch (myresolve) { case 0: mySpEAttr = 0; break; default: mySpEAttr = values.m_resolve_mod; } break; } setAttrs({ repeating_specials_AttrSpec1:mySpAttr, repeating_specials_MAttrSpec1:mySpEAttr }); }); }); Thanks for any insights!
1657543598
Finderski
Pro
Sheet Author
Compendium Curator
If I had to guess, it's because attributes are be default strings.  0 is not the same "0" in JS.  If you parse your values to integers, your case statement should work properly.  For example: let mystrength = parseInt(values.m_strength) || 0; That will take the value of m_strength and make it an integer, unless the value is not a number, in which case it will default to 0.
1657548672

Edited 1657549511
Dave
Pro
Thank you for the reply! Unfortunately, when I tried that it's returning nothing but 0s for those values. I'm not sure why that would be, since it was pulling workable values for them before. Near as I can tell, parseInt was returning 0s even with values in those attributes.
1657551645
Finderski
Pro
Sheet Author
Compendium Curator
You may need to add some logging to the sheetworker to help troubleshoot it a bit.  Something like this: console.log(`m_strength before parse: ${value.m_strength}`); let mystrength = parseInt(values.m_strength) || 0; console.log(`mystrength: ${mystrength}`); Then the Dev console will show you what the values are before parsing and after parsing. If you can share the details of the dev console, that will help us better be able to tell what's going on.
1657551792

Edited 1657552138
Dave
Pro
Stupid question, I'm afraid: what is the dev console? When I add those lines of code, the sheetworker stops doing anything at all.
1657553061
Finderski
Pro
Sheet Author
Compendium Curator
Those lines shouldn't have broken the code, so you'll likely need to repost your updated code so we can see what it looks like.   The dev console is a way to see what's going on with a particular page. I'm assuming you're using Chrome, so... I think F11 should display the console (if you're using Windows, I could be wrong on that as I use a Mac), if you use a Mac, Option+Cmd+i should display the Dev Console. If you can't get either of those to work, clicking on the main Chrome menu (how you get to Settings, etc) should have an option for More Tools and under that Developer Tools (that's the Dev Console). That will have various tabs, one is called Console, that tab will display any error messages, etc.
1657569662

Edited 1657570785
GiGs
Pro
Sheet Author
API Scripter
Going back to an earlier reply: Dave said: Thank you for the reply! Unfortunately, when I tried that it's returning nothing but 0s for those values. I'm not sure why that would be, since it was pulling workable values for them before. Near as I can tell, parseInt was returning 0s even with values in those attributes. This is very likely revealing your problem. We'll need to see the HTML for your attributes, at the very least strength, m_strength, and m_strength_mod attributes (so we have one of each). it's very likely the problem isn't in your sheet worker directly, but how the html and sheet worker work together. Are any of these autocalc attributes? Those are the ones where you set as disabled. If so, they are incompatible with sheet workers, and you'll need to replace then with sheet workers. Some unrelated suggestions: It isn't very clear what your code is doing, because your using attributes like mySpAttr and mySpEAttr - you can type out the full name of things, there is no practical reason any more to use short names. This isn't very clear, either: repeating_specials_AttrSpec1:mySpAttr, repeating_specials_MAttrSpec1:mySpEAttr The names on both sides of the colons can be made a lot more readable. Also there are two other things you really need to fix: Look at this line: on("change:repeating_specials:SpecAttrName", function() { Whatever the case of your original attributes, you need to use completely lower case here. So that should be: on("change:repeating_specials:specattrname", function() { A sheet worker will (sometimes) work with upper case letters there - until it doesn't, and it will stop working unpredictably, levaing you confused about the issue. Best to take care of it now. In fact, you can use lower case everywhere in a sheet worker, regardless of the original case of the attribute, and because of the on('change:lower_case_only need, its a good idea to do that. Secondly, you will need to put the other attributes being used on the change line, like on("change:repeating_specials:specattrname change:m_strength change:strength_m (etc)", function() { This is because the repeating section attribute wont change if the linked attributes change - it will become out of sync if you don't already have the worker set to watch those values. Edit: just realised this won't work in the sheet worker's current format, so ignie this suggestion. Finally, there's a lot of duplication in your code (the big switch function), that can be massively simplified, but I think I'd need a better description of what the sheet worker was doing to make that.
1657572506

Edited 1657575176
Dave
Pro
Thanks for all the information! Sounds like the biggest problem is that, yes, the value I need to work with is from an auto-calculating (disabled) field. I have no idea how to even begin to go about usefully fixing that, unfortunately. I just have no idea what I'm doing in JavaScript. Example: <input type="number" class="sheet-total" style="width:30px" title="m_strength" name="attr_m_strength" value="[[(@{m_strength_base}+@{m_strength_bonus})]]" disabled> Basically, m_strength_base is set by the player during character creation, while m_strength_bonus comes from other advantages/powers/situations, usually set by chatsetattr from an external macro that's run as a token action from the Roll20 tabletop. So, to convert it to a sheetworker, I'd need to figure out when it needs to check to update these things (which would be the "change:_m_strength_base change:m_strength_bonus," etc., I'd guess?), then have that trigger a sheetworker to kick out an actual number value instead of an auto-calculating field? That seems the most likely problem, based on what you're saying. That said, here is my current code for the sheetworker. What it should do is take spec_attribute_name (input by the player as a string, such as "Strength") and use it to find the corresponding values for character's standard attribute (strength), mega attribute (m_strength), and the modifier associated with the mega attribute (m_strength_mod). The part I can't get to work (I'm guessing because of the auto-calculating issue noted above) is that it can only read the mega attribute (stored as "spec_mega_attribute" in the updated version) as a 0. on("change:repeating_specials:specattrname change:strength change:m_strength_base change:m_strength_bonus change:charisma change:m_charisma_base change:m_charisma_bonus change:intelligence change:m_intelligence_base change:m_intelligence_bonus change:dexterity change:m_dexterity_base change:m_dexterity_bonus change:manipulation change:m_manipulation_base change:m_manipulation_bonus change:wits change:m_wits_base change:m_wits_bonus change:stamina change:m_stamina_base change:m_stamina_bonus change:composure change:m_composure_base change:m_composure_bonus change:resolve change:m_resolve_base change:m_resolve_bonus", function() { getAttrs(["repeating_specials_SpecAttrName","strength","m_strength","m_strength_mod","charisma","m_charisma","m_charisma_mod","intelligence","m_intelligence","m_intelligence_mod","dexterity","m_dexterity","m_dexterity_mod","manipulation","m_manipulation","m_manipulation_mod","wits","m_wits","m_wits_mod","stamina","m_stamina","m_stamina_mod","composure","m_composure","m_composure_mod","resolve","m_resolve","m_resolve_mod"], function(values) { let spec_attribute_name = values.repeating_specials_SpecAttrName; let spec_attribute = 0; let spec_mega_attribute = 0; let spec_mega_attribute_mod = 0; switch (spec_attribute_name) { case "Strength": spec_attribute = values.strength; spec_mega_attribute = values.m_strength; switch (values.m_strength) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_strength_mod; } break; case "Charisma": spec_attribute = values.charisma; spec_mega_attribute = values.m_charisma; switch (values.m_charisma) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_charisma_mod; } break; case "Intelligence": spec_attribute = values.intelligence; spec_mega_attribute = values.m_intelligence; switch (values.m_intelligence) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_intelligence_mod; } break; case "Dexterity": spec_attribute = values.dexterity; spec_mega_attribute = values.m_dexterity; switch (values.m_dexterity) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_dexterity_mod; } break; case "Manipulation": spec_attribute = values.manipulation; spec_mega_attribute = values.m_manipulation; switch (values.m_manipulation) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_manipulation_mod; } break; case "Wits": spec_attribute = values.wits; spec_mega_attribute = values.m_wits; switch (values.m_wits) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_wits_mod; } break; case "Stamina": spec_attribute = values.stamina; spec_mega_attribute = values.m_stamina; switch (values.m_stamina) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_stamina_mod; } break; case "Composure": spec_attribute = values.composure; spec_mega_attribute = values.m_composure; switch (values.m_composure) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_composure_mod; } break; case "Resolve": spec_attribute = values.resolve; spec_mega_attribute = values.m_resolve; switch (values.m_resolve) { case 0: spec_mega_attribute_mod = 0; break; default: spec_mega_attribute_mod = values.m_resolve_mod; } break; } setAttrs({ repeating_specials_attribute_spec1:spec_attribute, repeating_specials_mattribute_spec1:spec_mega_attribute, repeating_specials_mattribute_mod_spec1:spec_mega_attribute_mod }); }); }); Finally, I use Firefox, not Chrome, but whenever I put in those lines about console logging the sheetworker stops working altogether. I'll see if it has the same sort of feature, and maybe that will help? A friend showed me the dev console feature in Firefox, but I'm not sure it output anything relevant, and I don't really know what I'm looking for enough to look for it. There was a lot in there. That aside, it does sound like you've identified the likely suspect. I'll try to figure out another approach for those calculations. Thanks again for all the help!
1657577959

Edited 1657578560
GiGs
Pro
Sheet Author
API Scripter
I use firefox predominantly, and as you've found out, it does let you view the console. Reading the console (whether in firefox or chrome) is another matter! So, as you've found, your main issue is here: <input type="number" class="sheet-total" style="width:30px" title="m_strength" name="attr_m_strength" value="[[(@{m_strength_base}+@{m_strength_bonus})]]" disabled> The problem is: sheet workers can not read the value of attributes being calculated this way - they just see the expression: "[[(@{m_strength_base}+@{m_strength_bonus})]]" - not the score it calculates to. You need a sheet worker to calculate this value. It's not very hard - though a lot harder than using an autocalc field, admittedly. The first thing to do is to change the above html to <input type="number" class="sheet-total" style="width:30px" title="m_strength" name="attr_m_strength" value="0" readonly> Then you need to calculate its value in a sheet worker, like this: on ( 'change:m_strength_base change:m_strength_bonus' , () => {   getAttrs ([ 'm_strength_base' , 'm_strength_bonus' ], values => {     let m = parseInt ( v . m_strength_base ) || 0 ;     let m_bonus = parseInt ( v . m_strength_bonus ) || 0 ;     let m_stat = m + m_bonus ;     setAttrs ({       m_strength : m_stat     });   }); }); You'd need one of those for each attribute, but there's a way to simplify it. First though, do either of the _base or m_stat_bonus attributes themselves use autocalc attributes? If so, they'd need changing to sheet workers. Once you start using sheet workers, it cascades through your sheet - and a lot of old autocalcs need to be updated. Now you can handle multiple attributes in several ways, but one method is what I've called Universal Sheet Workers - essentially creating the code once, and applying it to multiple attributes that are calculated the same way. So your first create an array of all the attributes, then loop through them one by one, creating a sheet worker for each. That would look like this: const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core. forEach ( function (stat) { on ( `change:m_${stat}_base change:m_${stat}_bonus` , function () { getAttrs ([` m_${stat}_base`, ` m_${stat}_bonus` ], function (values) { const stat_base = parseInt (values[` m_${stat}_base` ]) | | 0 ; const stat_bonus = parseInt (values[` m_${stat}_bonus ` ]) | | 0 ;             const final_total = stat_base + stat_bonus; setAttrs ({ [`m_${stat}`]: final_total }); }); }); }); This should calculate all the m_stat scores for all the attributes you define in the initial array: const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; Just make sure that line includes the full list of attributes, and test if it works. Remember you need to change the original html to readonly instead of disabled. The forEach line basically loops through each stat named in the stats_core array, stores that attribute's name in the variable stat , and then uses that to dynamically create the sheet worker that follows. So it'll work for any number of stats, as long as they are properly named in the original array, and the other linked attributes, like m_strength_base, m_strength_bonus, are all named using the same format. If that works, your bigger repeating section sheet worker should start working, too. I'll make another post how to streamline that, using a similar principle.
A friend of mine actually helped me fix the math that was causing the initial problem, so I won't need to do the part of this that was giving me trouble! However, I will very likely still implement that change from the auto-calculation to the sheetworker, as it looks like a much more elegant way to handle things, as well as probably being less resource-intensive (I assume!). Thanks so much for all the help. If nothing else, I learned some things, and I'm very grateful for that!
1657580295
GiGs
Pro
Sheet Author
API Scripter
It is a lot less resource-itensive!
1657929758

Edited 1657929970
Dave
Pro
Sorry to revive this thread, but I thought I'd toss this question out there in case anyone can help. This is my current code for the sheetworker noted here, and it works exactly as intended. What I'm wondering is: is there a way to turn this into a kind of "template" sheetworker, so I could do the same thing for different attributes without having to duplicate all this code? This is more an "I'd like to get better at doing these" thing than a "this is a problem" thing, but I do like learning this stuff! on("change:repeating_specials:specattrname change:strength change:m_strength_base change:m_strength_bonus change:charisma change:m_charisma_base change:m_charisma_bonus change:intelligence change:m_intelligence_base change:m_intelligence_bonus change:dexterity change:m_dexterity_base change:m_dexterity_bonus change:manipulation change:m_manipulation_base change:m_manipulation_bonus change:wits change:m_wits_base change:m_wits_bonus change:stamina change:m_stamina_base change:m_stamina_bonus change:composure change:m_composure_base change:m_composure_bonus change:resolve change:m_resolve_base change:m_resolve_bonus", function() { getAttrs(["repeating_specials_SpecAttrName","strength","m_strength","m_strength_mod","charisma","m_charisma","m_charisma_mod","intelligence","m_intelligence","m_intelligence_mod","dexterity","m_dexterity","m_dexterity_mod","manipulation","m_manipulation","m_manipulation_mod","wits","m_wits","m_wits_mod","stamina","m_stamina","m_stamina_mod","composure","m_composure","m_composure_mod","resolve","m_resolve","m_resolve_mod"], function(values) { let spec_attribute_name = values.repeating_specials_SpecAttrName; let spec_attribute = 0; let spec_mega_attribute = 0; let spec_mega_attribute_mod = 0; switch (spec_attribute_name) { case "Strength": spec_attribute = values.strength; spec_mega_attribute = values.m_strength; spec_mega_attribute_mod = values.m_strength_mod; break; case "Charisma": spec_attribute = values.charisma; spec_mega_attribute = values.m_charisma; spec_mega_attribute_mod = values.m_charisma_mod; break; case "Intelligence": spec_attribute = values.intelligence; spec_mega_attribute = values.m_intelligence; spec_mega_attribute_mod = values.m_intelligence_mod; break; case "Dexterity": spec_attribute = values.dexterity; spec_mega_attribute = values.m_dexterity; spec_mega_attribute_mod = values.m_dexterity_mod; break; case "Manipulation": spec_attribute = values.manipulation; spec_mega_attribute = values.m_manipulation; spec_mega_attribute_mod = values.m_manipulation_mod; break; case "Wits": spec_attribute = values.wits; spec_mega_attribute = values.m_wits; spec_mega_attribute_mod = values.m_wits_mod; break; case "Stamina": spec_attribute = values.stamina; spec_mega_attribute = values.m_stamina; spec_mega_attribute_mod = values.m_stamina_mod; break; case "Composure": spec_attribute = values.composure; spec_mega_attribute = values.m_composure; spec_mega_attribute_mod = values.m_composure_mod; break; case "Resolve": spec_attribute = values.resolve; spec_mega_attribute = values.m_resolve; spec_mega_attribute_mod = values.m_resolve_mod; break; } setAttrs({ repeating_specials_attribute_spec1:spec_attribute, repeating_specials_mattribute_spec1:spec_mega_attribute, repeating_specials_mattribute_mod_spec1:spec_mega_attribute_mod }); }); }); Thanks for reading and, of course, for any thoughts anyone might have!
1657931113
GiGs
Pro
Sheet Author
API Scripter
The method I showed earlier, using a forEach loop, is exactly what you need for that. That's what it does.
1657931516

Edited 1657931684
Dave
Pro
Well, ask a silly question! I actually forgot you'd shown me that. My bad. I don't understand how what you posted shows that, but I'll keep trying to understand it. Thanks. :)
1657932058
GiGs
Pro
Sheet Author
API Scripter
You really should have asked questions about what you didn't understand after that last post, instead of saying someone else had showed you a solution. I'm a bit grumpy and reluctant to spend any time helping at the moment, because another of the my answers has been selfishly deleted by the thread asker. It's not your fault, but makes me sigh and wonder what is the point if the work i put in here can be instantly deleted. But look at this post: <a href="https://app.roll20.net/forum/permalink/10967757/" rel="nofollow">https://app.roll20.net/forum/permalink/10967757/</a> And if you have any questions, ask away and I'll try to explain when I'm less grumpy :)
1657933095

Edited 1657933191
Dave
Pro
Fist of all, that sucks, and I'm sorry to hear it. People should respect the work that goes into helping with these requests. I do apologize for not asking better clarifying questions at the time, but I was pretty burned out on code just then and needed a break, and I also figured I could parse it out for myself if I took the time. So, today, I think I understood what the code you showed before does; it seems to go through the list of attributes and calculates modifiers for all of them? That's a cool bit of code that I'm planning to try to use, but it's not quite the problem I'm currently trying to solve. (It would be a better way to code my calculations, but with the help I got to do better math my calculations are more accurate and don't need as complicated a sorting process; thus, the sheetworker doesn't need the nested case statements, but it's still making a selection that I need to complete a roll macro in the sheet.) The sheetworker is designed to take the name of an attribute and then return three numerical values associated with that attribute, which are calculated elsewhere. I was just wondering if there were a way to create a sheetworker (perhaps what you called a "universal" sheet worker) that would basically create a function that I could plug a few attribute names into, have it perform the same work as the one I most recently posted, and then just basically have my sheetworkers that duplicate that work just be, essentially, function calls rather than nearly-duplicate sheetworkers. I just don't understand JavaScript well enough to either do (or, I'm sorry to say, properly articulate) what I'd like to do. I'll include an example of a similar sheetworker, below, that does a totally different thing but shows the kind of "function" effect I'd like to create. Example: The kind of "function" sheetworker I'd like to create As an example, I've seen a sheetworker that creates a way to "total" values from a repeating section, but it lets you then create additional sheetworkers based on it, so each additional sheetworker is very concise: on("change:repeating_styles:stylelevel change:repeating_styles:styledesc remove:repeating_styles", function() { &nbsp;&nbsp; &nbsp;repeatingSum("stylestotal","styles","stylelevel"); }); I've tried studying the sheetworker that creates this function, but it's way over my head. I'll include the actual code here, too: const repeatingSum = (destinations, section, fields) =&gt; { if (!Array.isArray(destinations)) destinations = [destinations.replace(/\s/g, '').split(',')]; if (!Array.isArray(fields)) fields = [fields.replace(/\s/g, '').split(',')]; getSectionIDs(`repeating_${section}`, idArray =&gt; { const attrArray = idArray.reduce((m, id) =&gt; [...m, ...(fields.map(field =&gt; `repeating_${section}_${id}_${field}`))], []); getAttrs([...attrArray], v =&gt; { const getValue = (section, id, field) =&gt; v[`repeating_${section}_${id}_${field}`] === 'on' ? 1 : parseFloat(v[`repeating_${section}_${id}_${field}`]) || 0; const commonMultipliers = (fields.length &lt;= destinations.length) ? [] : fields.splice(destinations.length, fields.length - destinations.length); const output = {}; destinations.forEach((destination, index) =&gt; { output[destination] = idArray.reduce((total, id) =&gt; total + getValue(section, id, fields[index]) * commonMultipliers.reduce((subtotal, mult) =&gt; subtotal * getValue(section, id, mult), 1), 0); }); setAttrs(output); }); }); };
1657933748
GiGs
Pro
Sheet Author
API Scripter
Hehe that's one of my functions. (And thanks for the comments in your first paragrapph.) So I'm not sure what you're asking for. The repeatingSum function is designed to generate a single total, but it looks like that's not what you're asking for. Are you asking for a way to get rid of the big switch in your last post, and get the 3 stats output for whiever stat is input?
1657935447

Edited 1657937008
Dave
Pro
I think I'm just asking how to create a function. I want to be able to create one big block of code, then just reference it for each set of attributes I need to plug into it. So, for instance, instead of "repeating_specials" I might want to plug similar values from "repeating_arts" into it with a function call like the one I referenced in my last post. That way, for each roll type I need to pull those attribute values for, I can just call the function instead of having a whole additional sheetworker that's 90% a duplication of a sheetworker I already have. Like, shooting from the hip... const getAttributeValues = (attribute, m_mattribute, m_attribute_mod, section, fields) =&gt; { ** Here would be code equivalent to my sheetworker, above, but adaptable to whatever repeating section/field/etc. I needed ** } Then, I'd call it with: on("change:repeating_arts:art_attribute_name change:strength change:m_strength_base change:m_strength_bonus change:charisma change:m_charisma_base change:m_charisma_bonus change:intelligence change:m_intelligence_base change:m_intelligence_bonus change:dexterity change:m_dexterity_base change:m_dexterity_bonus change:manipulation change:m_manipulation_base change:m_manipulation_bonus change:wits change:m_wits_base change:m_wits_bonus change:stamina change:m_stamina_base change:m_stamina_bonus change:composure change:m_composure_base change:m_composure_bonus change:resolve change:m_resolve_base change:m_resolve_bonus", function() { &nbsp;&nbsp;&nbsp;&nbsp;getAttributeValue("attribute_spec1 mattribute_spec1 attribute_spec_mod1","specials","SpecAttrName"); } I'm sure my syntax is off, but something to that effect is what I'd like. I hope that makes some sense!
1657939032

Edited 1657939438
GiGs
Pro
Sheet Author
API Scripter
I'm going to explain a problem you currently face but may not have realised yet, and ask a question at the end. One problem you will have, and already have in the sheetworker you posted above, and definitely have in the suggested function structure you have, is that you aren't properly accounting for rows in a repeating section . The sheet worker that starts like this: on("change:repeating_specials:specattrname change:strength change:m_strength_base change:m_strength_bonus change:charisma change:m_charisma_base change:m_charisma_bonus change:intelligence change:m_intelligence_base change:m_intelligence_bonus change:dexterity change:m_dexterity_base change:m_dexterity_bonus change:manipulation change:m_manipulation_base change:m_manipulation_bonus change:wits change:m_wits_base change:m_wits_bonus change:stamina change:m_stamina_base change:m_stamina_bonus change:composure change:m_composure_base change:m_composure_bonus change:resolve change:m_resolve_base change:m_resolve_bonus", function() { getAttrs(["repeating_specials_SpecAttrName","strength","m_strength","m_strength_mod","charisma","m_charisma","m_charisma_mod","intelligence","m_intelligence","m_intelligence_mod","dexterity","m_dexterity","m_dexterity_mod","manipulation","m_manipulation","m_manipulation_mod","wits","m_wits","m_wits_mod","stamina","m_stamina","m_stamina_mod","composure","m_composure","m_composure_mod","resolve","m_resolve","m_resolve_mod"], function(values) { The problem is this part: "repeating_specials_SpecAttrName","strength", The syntax for that repeating attribute is only meant for use when the initial change is from a single row of the repeatign section. This is because sheet worker attributes actually look like this: "repeating_specials_heiyt8w7hbwh7_SpecAttrName" That string of random letters and numbers is the row id. Every repeating attribute is on a row in the section, and you need to tell roll20 what row to work on. But roll20 has a shortcut: when, and only when, you are working within a row of a repeating section, roll20 knows which row is being changed and can supply the id automatically, so you can use this syntax: "repeating_specials_SpecAttrName" Remember roll20 still needs the row id, but it supplies it itself behind the scenes. However, when a sheet worker is triggered from outside the repeating section, like say, strength changes, roll20 doesn't know the row id to work on - the change affects every row in the repeating section, not just one row. But that syntax is for just one row change. So to handle this, you need to use the getSectionIDs function. That creates an array of all the row ids in the section (one id for each row), which you can then work with. The sheet worker I posted above was for static attributes, that aren't in a repeating section. This is going to affect any function you create. So any function will need to properly address all rows of the repeating section, and will have to use getSectionIds to identify those rows. So three questions I need answering are : Are you looking for a function that will create those these attributes for each repeating section? Are the 3 attributes always named the same way: attribute_spec1, mattribute_spec1, mattribute_mod_spec1 Are they always created the same way (by checking one of the static attributes, and getting the values of [name], m_[name], and m_[name]_mod) If the answer to any of these questions is no, decribe the correct answer please. I expect that some things are wrong, since your attributes end with spec1 , which suggests the name is based on the repeating section name.
1657940612

Edited 1657940661
Dave
Pro
I understand what you're saying about repeating section IDs, though I don't understand what to do about it at all (and I am not familiar with getSectionIDs, though I can broadly assume what it does). As to your questions: No, maybe? Not every single repeating section, but for various repeating sections as needed. I need to be able to call the function in relation to only the repeating sections that need to use these attributes for their rolls. If it's not going to lag the sheet or anything, then I guess it wouldn't hurt if all repeating sections could generate these attributes. No, but if needed they could follow the same pattern . The pattern would be: atttribute_TYPE, mattribute_TYPE, and mattribute_mod_TYPE Yes I hope those answers make sense!
1657940982

Edited 1657941447
GiGs
Pro
Sheet Author
API Scripter
Haha, I realise My question 1 was imprecise but you answered it anyway. For question 2, they would need to follow the same pattern. Is TYPE easily derived from the repeating section name? (that is, can a computer program apply a rule to the repeating section name and get the correct type?) I'm asking because you have asked for a particular format (a function that you can create), which can be done, but there is a better way to write this, and I'm trying to work out if it can be used. That would need a rule for the TYPE. For instance, for repeating_specials, the type could be specials, and for repeating_art, the TYPE could be art, leading to attributes like repeating_specials_attribute_specials repeating_specials_mattribute_specials repeating_specials_mattribute_mod_specials repeating_art_attribute_art repeating_art_mattribute_art repeating_art_mattribute_mod_art See how the same rule can be used to create the full attribute names for each section? What method of creating the TYPE would you like to use? Note that it doesnt need to be programmatical - you could have a TYPE whose name doesn't relate to the repeating section by a simple name, but that does make the code easier.
I could easily adapt to following the pattern for TYPE that you've indicated, certainly, with it matching repeating_TYPE. I'm all for easier. :)
1657942777

Edited 1657964698
GiGs
Pro
Sheet Author
API Scripter
Okay, here's a universal sheet worker that should work for every repeating section you declare. It uses some complex code - you don't need to understand it, but I can try to explain it if you have questions. That said, you only need to edit the first two lines: First line: add each repeating section name (without the repeating_ part) you want to create attributes for. Second line: Make sure the list of attribute names is correct and complete. Nothing else needs to be edited. Once you have the stats you need on the second line, the only change you need to make is to add to the first line whenever you need to. Just add one word there for a section and the stats for that section will be created. const sections = [ 'specials' , 'art' ]; const stats_core = [ 'Strength' , 'Dexterity' , 'Charisma' , 'Intelligence' , 'Manipulation' , 'Wits' , 'Stamina' , 'Composure' , 'Resolve' ]; const stats_all = stats_core . reduce (( all , one ) =&gt; [... all , one , `m_ ${ one } ` , `m_ ${ one } _mod` ], []); const stat_changes = stats_all . reduce (( changes , stat ) =&gt; `change: ${ stat } ${ changes } ` , '' ). slice ( 0 ,- 1 ). toLowerCase (); sections . forEach ( section =&gt; { &nbsp; const special_attribute_name_string = 'attribute_name' ; &nbsp; on ( ` ${ stat_changes } change:repeating_ ${ section } : ${ special_attribute_name_string } ` , () =&gt; { &nbsp; &nbsp; getSectionIDs ( `repeating_ ${ section } ` , id_array =&gt; { &nbsp; &nbsp; &nbsp; const fieldnames = id_array . reduce (( all , id ) =&gt; [... all , `repeating_ ${ section } _ ${ id } _ ${ special_attribute_name_string } ` ], []); &nbsp; &nbsp; &nbsp; getAttrs ([... fieldnames , ... stats_all ], values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const output = {}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; id_array . forEach ( id =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const special_attribute_name = values [ `repeating_ ${ section } _ ${ id } _ ${ special_attribute_name_string } ` ]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output [ `repeating_ ${ section } _ ${ id } _attribute_ ${ section } ` ] = + values [ special_attribute_name ] || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output [ `repeating_ ${ section } _ ${ id } _mattribute_ ${ section } ` ] = + values [ `m_ ${ special_attribute_name } ` ] || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; output [ `repeating_ ${ section } _ ${ id } _mattribute_mod_ ${ section } ` ] = + values [ `m_ ${ special_attribute_name } _mod` ] || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ( output ); &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); &nbsp; }); }); I havent test this, because I don't have the html to test it on, so let me know if it works or not :)
Okay, attempting to test this. What is the attribute that conveys the actual attribute name as input? I can't figure that out.
1657946621

Edited 1657946807
GiGs
Pro
Sheet Author
API Scripter
If I understand your question, that is specattrname - it's defined on this line: const special_attribute_name_string = 'specattrname'; So if you need to change it, you can change it on that one line without changing it in multiple places in the code. Note that case doesn't matter here - specattrname is the same as SpecAttrName, except the event line (the one starting on( ) must be in lower case.
Aha. Now, that is set within a repeating section. Do I need to adjust for that?
1657948487

Edited 1657948679
GiGs
Pro
Sheet Author
API Scripter
No, the code already assumes it is set in a repeating section. It is needed to be set on a specific row, after all. For example look at this line: const special_attribute_name = v [ `repeating_ ${ section } _ ${ id } _ ${ special_attribute_name_string } ` ]; But in your HTML, you'll define it just as &lt;input type="text" name="attr_specattrname"&gt; or whatever - its probably a select dropdown, now an input. The thml part doesnt have the repeating section name or row id included, that gets added automatically. I took specattrname from your earlier code, personally I'd use something longer, like special_attribute, but you can change it to whatever you want (just make sure its the same in your html and in your sheet worker)
1657948797
GiGs
Pro
Sheet Author
API Scripter
I've just realised that code includes in error: this const special_attribute_name = v [ `repeating_ ${ section } _ ${ id } _ ${ special_attribute_name_string } ` ]; should be const special_attribute_name = values [ `repeating_ ${ section } _ ${ id } _ ${ special_attribute_name_string } ` ]; I'll change the above worker to account for that.
1657950169

Edited 1657950192
Dave
Pro
I really appreciate all the help! Unfortunately, I haven't been able to make it work. Here's my HTML; note that I changed the selected attribute to "attribute_name" so it wouldn't cause confusion in other repeating sections: &lt;select class="sheet-altselect" name="attr_attribute_name" title="Select Roll Attribute"&gt; &lt;option value="none"&gt;--&lt;/option&gt; &lt;option value="Strength"&gt;STR&lt;/option&gt; &lt;option value="Dexterity"&gt;DEX&lt;/option&gt; &lt;option value="Stamina"&gt;STA&lt;/option&gt; &lt;option value="Charisma"&gt;CHA&lt;/option&gt; &lt;option value="Manipulation"&gt;MAN&lt;/option&gt; &lt;option value="Composure"&gt;COM&lt;/option&gt; &lt;option value="Intelligence"&gt;INT&lt;/option&gt; &lt;option value="Wits"&gt;WIT&lt;/option&gt; &lt;option value="Resolve"&gt;RES&lt;/option&gt; &lt;/select&gt; &lt;input type="hidden" name="attr_attribute_specials" value="0" readonly/&gt; &lt;input type="hidden" name="attr_mattribute_specials" value="0" readonly/&gt; &lt;input type="hidden" name="attr_mattribute_mod_specials" value="0" readonly/&gt; And here is my copy of the sheetworker, just in case I messed something up: const sections = ['specials', 'arts']; const stats_core = ['strength', 'dexterity', 'stamina', 'charisma', 'manipulation', 'composure', 'intelligence', 'wits', 'resolve']; const stats_all = stats_core.reduce((all, one) =&gt; [...all, one, `m_${one}`, `m_${one}_mod`], []); const stat_changes = stats_all.reduce((changes, stat) =&gt; `${changes} ${stat}`, '').slice(0,-1).toLowercase(); sections.forEach(section =&gt; { const special_attribute_name_string = 'attribute_name'; on(`${stat_changes} change:repeating_${section}:${special_attribute_name_string}`, () =&gt; { getSectionIDs(`repeating_${section}`, id_array =&gt; { const fieldnames = id_array.reduce((all, id) =&gt; [...all, `repeating_${section}_${id}_${special_attribute_name_string}`], []); getAttrs([...fieldnames, ...stats_all], values =&gt; { const output = {}; id_array.forEach(id =&gt; { const special_attribute_name = values[`repeating_${section}_${id}_${special_attribute_name_string}`]; output[`repeating_${section}_${id}_attribute_${section}`] = +values[special_attribute_name] || 0; output[`repeating_${section}_${id}_mattribute_${section}`] = +values[`m_${special_attribute_name}`] || 0; output[`repeating_${section}_${id}_mattribute_mod_${section}`] = +values[`m_${special_attribute_name}_mod`] || 0; }); setAttrs(output); }); }); }); });
1657964663

Edited 1657964794
GiGs
Pro
Sheet Author
API Scripter
There were two errors - one of them was mine, and one seems to be undocumentated behaviour of Roll20 that is working oddly. The first error: the const stat_changes line should be this: const stat_changes = stats_all . reduce (( changes , stat ) =&gt; `change: ${ stat } ${ changes } ` , '' ). slice ( 0 ,- 1 ). toLowerCase (); I somehow overlooked the 'change:' part of that code, which is the whole point of that function. Oops. The second one is more baffling. The stats_core line needs changing to const stats_core = [ 'Strength' , 'Dexterity' , 'Charisma' , 'Intelligence' , 'Manipulation' , 'Wits' , 'Stamina' , 'Composure' , 'Resolve' ]; Whether attributes are capitalised is not supposed to matter here, but seems to matter in exactly one case: the attributes listed in a select must match the case perfectly. The bizarre thing is, this only seems to matter for select, and doesn't matter for inputs. Considering it's not how other HTML behaves, I think it's a weird bug. generally its good advice to use lower case for attribute names everywhere, because of the event line of sheet workers, and if you use the same case in HTML as sheet workers, you'll never notice this behaviour. So it could have been around awhile, and i just haven't noticed. I'll correct the big worker with this two lines.
I've been trying to switch to all lower case, though in this instance (since the text needs to display in the roll) I really wanted to have proper capitalization for the output to look good--but that does seem like an odd bug, for sure. Unfortunately, the worker doesn't seem to be updating the attributes. I tried copying over your version fresh just in case I'd made some error on my end, but the fields don't update when I change the value of attribute_name within any of the sections. Sorry this is being such a problem. If it just won't work, I do still have my old version as a fallback option. I really appreciate all your work, though! You have the patience of a saint to offer so much help to us hapless dabblers. :)
1658007807
GiGs
Pro
Sheet Author
API Scripter
It sounds like you're getting frustrateed, or expecting me to get frustrated, But you're right, on this topic I do have the patience of a saint - we'll get there eventually. i really think we are in the final stretch. The problem is: it does work at my end. Your recent post that included some html let me mock up some html of my own, and I tested the last sheet worker with it, and it was working fine. So it's likely there is something in your html that is interfering or that I have not anticipated correctly how it is written. The best way to proceed now is for you to post your entire html , at a place called pastebin.com. I can then try it out and figure out why the sheet worker doesnt work for you. Bear in mind, there are potentially lots of ways to break it, so I cant guess ahead of time. I have to see your html.
I'm not so much frustrated as not wanting to take up too much of your time! Unfortunately, my entire HTML is too large for PasteBin, so I've had to cut it down a bit. I think the sections I've removed shouldn't have any strong impact on the actual relevant parts, though: <a href="https://pastebin.com/v8uZDq7b" rel="nofollow">https://pastebin.com/v8uZDq7b</a> I will note, this is a heavily WiP sheet (converting an old, much messier one to a newer, hopefully eventually less messy one), so I apologize in advance for what a rat's nest the HTML will be...but thank you!
1658009347
GiGs
Pro
Sheet Author
API Scripter
I;m surprised it's too big for pastebin. The best thing in that case is to cut into parts, and post each part separately. It's fine being a rats nest, you havent seen my sheets in progregress - especially my first sheet :)
1658009846
GiGs
Pro
Sheet Author
API Scripter
Here's your most likely problem: const sections = [ 'specials' , 'arts' , 'spaths' , 'ppaths' , 'dlores' . 'rituals' , 'gear' ]; Notice you;ve used a period after dlores instead of a comma. Correrct to this: const sections = [ 'specials' , 'arts' , 'spaths' , 'ppaths' , 'dlores' , 'rituals' , 'gear' ]; The code still won't work, because your base stats are autocalc attributes. Remember you have to remove those, and replace with readonly sheet workers. But that fixes the immediate problem.
1658018999

Edited 1658020736
Dave
Pro
Okay, that makes sense. I should have understood that. One question as I go to implement that change. It looks like the sheetworker you showed me will set the m_ATTRIBUTE value (m_strength, etc.), but do I need it to calculate the ATTRIBUTE value as well (strength, etc.)?
I tried my hand at updating your worker to include basic stats as well as m_stats, with this result--and it seems to work! stats_core.forEach(function (stat) { on(`change:${stat}_base change:${stat}_bonus m_${stat}_base change:m_${stat}_bonus`, function () { getAttrs([`${stat}_base`, `${stat}_bonus`, `m_${stat}_base`, `m_${stat}_bonus`], function (values) { const stat_base = parseInt(values[`${stat}_base`])||0; const stat_bonus = parseInt(values[`${stat}_bonus`])||0; const final_total = stat_base + stat_bonus; const m_stat_base = parseInt(values[`m_${stat}_base`])||0; const m_stat_bonus = parseInt(values[`m_${stat}_bonus`])||0; const final_m_total = stat_base + stat_bonus; setAttrs({ [`${stat}`]: final_total, [`m_${stat}`]: final_m_total }); }); }); }); Now, everything seems to work beautifully. I'll update if during more extensive testing I find any issues, but it's looking good. Thank you so much for all your help! I feel like this sheet is now working a lot smarter, it's better for player use, and you've even got me trying to clean up my HTML and trying to start making better use of the CSS file. So, wow--thank you so very much! My players will certainly know by whose hand this was made possible!
1658021785

Edited 1658023880
GiGs
Pro
Sheet Author
API Scripter
To calculate the strength, you need to add strength_base and strength_bonus, but there is no strength_bonus in the sheet you sent me. There is is a strength_bonus_display, but that isn't the same thing. Does strength_bonus exist? Possibly in the part of the sheet you omitted? I don't know how each of strength, m_strength, and m_strength_bonus are calculated (well, I do know how strength is calculated, see previous paragraph). If there is a consisent rule for these, and for each of the other attributes (charsima, dexterity, etc), it can be handled easily with a universal sheet worker. const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core. forEach ( function (stat) { on ( `change:m_${stat}_base change:m_${stat}_bonus` , function () { getAttrs ([` m_${stat}_base`, ` m_${stat}_bonus` ], function (values) { const stat_base = parseInt (values[` m_${stat}_base` ]) | | 0 ; const stat_bonus = parseInt (values[` m_${stat}_bonus ` ]) | | 0 ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const final_total = stat_base + stat_bonus; setAttrs ({ [`m_${stat}`]: final_total }); }); }); }); This template will create the m_strength, m_dexterity, m_charisma, etc. from the m_strength_base + m_strength_bonus. It would be easy to expand it to calculate all stats that are calculated the same way. But I'd need to know the names and rules for each of the stats you need to generate. Note that you already have the const stats_core line, you shouldn't repeat that. Whichever sheet worker is first will have it before it, but later sheet workers will use it whenever it referenced. const stands for contant - you are creating a single array of names that can be used by any sheet workers after that.
1658022716

Edited 1658022802
Dave
Pro
Okay, I was premature. I think the issue is that the m_attributes are live calculated, per the HTML below. &lt;input type="hidden" name="attr_m_strength_mod" value="[[((floor((@{m_strength} * (@{m_strength} - 1))/2) + 1) * ceil(@{m_strength}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_charisma_mod" value="[[((floor((@{m_charisma} * (@{m_charisma} - 1))/2) + 1) * ceil(@{m_charisma}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_intelligence_mod" value="[[((floor((@{m_intelligence} * (@{m_intelligence} - 1))/2) + 1) * ceil(@{m_intelligence}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_dexterity_mod" value="[[((floor((@{m_dexterity} * (@{m_dexterity} - 1))/2) + 1) * ceil(@{m_dexterity}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_manipulation_mod" value="[[((floor((@{m_manipulation} * (@{m_manipulation} - 1))/2) + 1) * ceil(@{m_manipulation}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_wits_mod" value="[[((floor((@{m_wits} * (@{m_wits} - 1))/2) + 1) * ceil(@{m_wits}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_stamina_mod" value="[[((floor((@{m_stamina} * (@{m_stamina} - 1))/2) + 1) * ceil(@{m_stamina}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_composure_mod" value="[[((floor((@{m_composure} * (@{m_composure} - 1))/2) + 1) * ceil(@{m_composure}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_resolve_mod" value="[[((floor((@{m_resolve} * (@{m_resolve} - 1))/2) + 1) * ceil(@{m_resolve}/15))]]" disabled="disabled" &gt; I'm not sure how to translate that into a sheetworker, as the math is more than I'd know how to approach. (A friend gave me these formulas.) Just in case there's another problem, I'll include my (attempt at a corrected) sheetworker as well, below: const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core.forEach(function (stat) { on(`change:${stat}_base change:${stat}_bonus m_${stat}_base change:m_${stat}_bonus`, function () { getAttrs([`${stat}_base`, `${stat}_bonus`, `m_${stat}_base`, `m_${stat}_bonus`], function (values) { const stat_base = parseInt(values[`${stat}_base`])||0; const stat_bonus = parseInt(values[`${stat}_bonus`])||0; const final_total = stat_base + stat_bonus; const m_stat_base = parseInt(values[`m_${stat}_base`])||0; const m_stat_bonus = parseInt(values[`m_${stat}_bonus`])||0; const final_m_total = m_stat_base + m_stat_bonus; setAttrs({ [`${stat}`]: final_total, [`m_${stat}`]: final_m_total }); }); }); }); I suspect there's a way to add a "const_m_stat_mod" line to that based on final_m_total? I may try doing so and see if I can muddle my way through translating the math.
1658023232
GiGs
Pro
Sheet Author
API Scripter
Dave said: Now, everything seems to work beautifully. I'll update if during more extensive testing I find any issues, but it's looking good. Thank you so much for all your help! I feel like this sheet is now working a lot smarter, it's better for player use, and you've even got me trying to clean up my HTML and trying to start making better use of the CSS file. So, wow--thank you so very much! My players will certainly know by whose hand this was made possible! That's good to hear, and thanks, hehe. From a quick glance at your sheet, it did seem like it was very amenable to sheet workers, that it wouldnt take that much work to replace the autocalc attributes. You do have a lot of repeating inline CVSS styles, and your sheet wold be better off if you moved them into classes in the CSS sheet. It seems like all your sheet workers at the end of the sheet can be grouped into two types, that each basically do the same thing, for miltiple different stats. The ones with for loops look like perfect candidates for streamlining with a single universal sheet worker. For example, these sheet workers: on ( "change:repeating_disciplines:disciplinelevel remove:repeating_disciplines" , function () { &nbsp; getSectionIDs ( "repeating_disciplines" , function ( IDArray ) { &nbsp; &nbsp; let fieldNames = []; &nbsp; &nbsp; for ( var i = 0 ; i &lt; IDArray . length ; i ++) { &nbsp; &nbsp; &nbsp; fieldNames . push ( "repeating_disciplines_" + IDArray [ i ] + "_disciplinelevel" ); &nbsp; &nbsp; } &nbsp; &nbsp; let total = 0 ; &nbsp; &nbsp; getAttrs ( fieldNames , function ( values ) { &nbsp; &nbsp; &nbsp; for ( var i = 0 ; i &lt; IDArray . length ; i ++) { &nbsp; &nbsp; &nbsp; &nbsp; total += parseInt ( values [ "repeating_disciplines_" + IDArray [ i ] + "_disciplinelevel" ]) || 0 ; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; disciplinestotal: total &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); &nbsp; }); }); on ( "change:repeating_gifts:giftlevel remove:repeating_gifts" , function () { &nbsp; getSectionIDs ( "repeating_gifts" , function ( IDArray ) { &nbsp; &nbsp; let fieldNames = []; &nbsp; &nbsp; for ( var i = 0 ; i &lt; IDArray . length ; i ++) { &nbsp; &nbsp; &nbsp; fieldNames . push ( "repeating_gifts_" + IDArray [ i ] + "_giftlevelcost" ); &nbsp; &nbsp; } &nbsp; &nbsp; let total = 0 ; &nbsp; &nbsp; getAttrs ( fieldNames , function ( values ) { &nbsp; &nbsp; &nbsp; for ( var i = 0 ; i &lt; IDArray . length ; i ++) { &nbsp; &nbsp; &nbsp; &nbsp; total += parseInt ( values [ "repeating_gifts_" + IDArray [ i ] + "_giftlevelcost" ]) || 0 ; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; giftstotal: total &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); &nbsp; }); }); Could be replaced by: const section_level_costs = [ 'gift' , 'discipline' ]; section_level_costs . forEach ( section =&gt; { &nbsp; on ( "change:repeating_" + section + "s:" + section + "level remove:repeating_" + section + "s" , function () { &nbsp; &nbsp; getSectionIDs ( "repeating_" + section + "s" , function ( IDArray ) { &nbsp; &nbsp; &nbsp; let fieldNames = []; &nbsp; &nbsp; &nbsp; for ( var i = 0 ; i &lt; IDArray . length ; i ++) { &nbsp; &nbsp; &nbsp; &nbsp; fieldNames . push ( "repeating_" + section + "s_" + IDArray [ i ] + "_" + section + "levelcost" ); &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; let total = 0 ; &nbsp; &nbsp; &nbsp; getAttrs ( fieldNames , function ( values ) { &nbsp; &nbsp; &nbsp; &nbsp; for ( var i = 0 ; i &lt; IDArray . length ; i ++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; total += parseInt ( values [ "repeating_" + section + "s_" + IDArray [ i ] + "_" + section + "levelcost" ]) || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [section + "stotal" ]: total &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); &nbsp; }); }); (Personally I'd streamline it further, but if you are comfortable with that syntax, it's better to use it.) If you compare it to the original workers, you can see I just looked at the bits that varied between each version, and noticed that part of the section name (gift, dicipline) was repeated in each part, and that could be used to turn it into a template. Then I just wrapped inside a forEach structure, so that each needed worker would be created for each item in the array. If your other workers of this type work all the same way, their names can be just added to the array at the start, and they automatically get generated.
1658023583

Edited 1658023681
GiGs
Pro
Sheet Author
API Scripter
Dave said: Okay, I was premature. I think the issue is that the m_attributes are live calculated, per the HTML below. &lt;input type="hidden" name="attr_m_strength_mod" value="[[((floor((@{m_strength} * (@{m_strength} - 1))/2) + 1) * ceil(@{m_strength}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_charisma_mod" value="[[((floor((@{m_charisma} * (@{m_charisma} - 1))/2) + 1) * ceil(@{m_charisma}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_intelligence_mod" value="[[((floor((@{m_intelligence} * (@{m_intelligence} - 1))/2) + 1) * ceil(@{m_intelligence}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_dexterity_mod" value="[[((floor((@{m_dexterity} * (@{m_dexterity} - 1))/2) + 1) * ceil(@{m_dexterity}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_manipulation_mod" value="[[((floor((@{m_manipulation} * (@{m_manipulation} - 1))/2) + 1) * ceil(@{m_manipulation}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_wits_mod" value="[[((floor((@{m_wits} * (@{m_wits} - 1))/2) + 1) * ceil(@{m_wits}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_stamina_mod" value="[[((floor((@{m_stamina} * (@{m_stamina} - 1))/2) + 1) * ceil(@{m_stamina}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_composure_mod" value="[[((floor((@{m_composure} * (@{m_composure} - 1))/2) + 1) * ceil(@{m_composure}/15))]]" disabled="disabled" &gt; &lt;input type="hidden" name="attr_m_resolve_mod" value="[[((floor((@{m_resolve} * (@{m_resolve} - 1))/2) + 1) * ceil(@{m_resolve}/15))]]" disabled="disabled" &gt; I'm not sure how to translate that into a sheetworker, as the math is more than I'd know how to approach. (A friend gave me these formulas.) Just in case there's another problem, I'll include my (attempt at a corrected) sheetworker as well, below: const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core.forEach(function (stat) { on(`change:${stat}_base change:${stat}_bonus m_${stat}_base change:m_${stat}_bonus`, function () { getAttrs([`${stat}_base`, `${stat}_bonus`, `m_${stat}_base`, `m_${stat}_bonus`], function (values) { const stat_base = parseInt(values[`${stat}_base`])||0; const stat_bonus = parseInt(values[`${stat}_bonus`])||0; const final_total = stat_base + stat_bonus; const m_stat_base = parseInt(values[`m_${stat}_base`])||0; const m_stat_bonus = parseInt(values[`m_${stat}_bonus`])||0; const final_m_total = m_stat_base + m_stat_bonus; setAttrs({ [`${stat}`]: final_total, [`m_${stat}`]: final_m_total }); }); }); }); I suspect there's a way to add a "const_m_stat_mod" line to that based on final_m_total? I may try doing so and see if I can muddle my way through translating the math. That is certainly doable, but might be done more elegantly than simply copying the code there. If I was at your table, and needed to calulate m_resolve_mod, tell me how you'd describe it (this means, without referring to roll20 or javascript in any way). Also it looks like you are trying to combine all the attributes (stta, m_stat, m_stat_mod) into a single sheet worker. You dont need to do that. Everything inside of the forEach loop will be repeated, so you can do this: stats_core.forEach(function (stat) { on(`change:${stat}_base change:${stat}_bonus`, function () { /* bunch of code snipped */ }); on(`change:m_${stat}_base change:m_${stat}_bonus`, function () { /* bunch of code snipped */ }); }); This is why I asked for all the rules - so I could show you the most efficient way to build that forEach loop.
1658023831
GiGs
Pro
Sheet Author
API Scripter
By the way, in case it's not apparent, this `change:${stat}_base change:${stat}_bonus` is exactly the same as "change:" + stat + "base change:" + stat + "bonus" They are different ways of writing the same thing. But there are advantages to the first version, and it is simpler to write and easier to read once you've gotten used to it.
The best explanation I can give is that m_ATTRIBUTE_mod takes m_ATTRIBUTE and does math to calculate a modifier. This is based on a formula derived from the game materials, so that if m_ATTRIBUTE is 1, m_ATTRIBUTE_mod is 1; it goes something like 1:1, 2:2:, 3:4, 4:7, and so on. So, I just need to take the value calculated for the m_ATTRIBUTE and do the math to convert it into the m_ATTRIBUTE_mod. I hope that makes sense?
1658025218
GiGs
Pro
Sheet Author
API Scripter
That doesn't make sense :) I was hoping for the details behind "does math"
This is the math: ((floor((m_ATTRIBUTE * (m_ATTRIBUTE - 1))/2) + 1) * ceil(m_ATTRIBUTE/15)) It's possible it translates to something like: ((Math.floor((final_m_total * (final_m_total - 1))/2) + 1) * Math.ceil(final_m_total/15)) I don't know how to explain the math better than that, unfortunately.
1658025718

Edited 1658025737
Dave
Pro
Here's my current attempt at updating the worker(s)? (It doesn't work, though.) const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core.forEach(function (stat) { on(`change:${stat}_base change:${stat}_bonus`, function () { getAttrs([`${stat}_base`, `${stat}_bonus`], function (values) { const stat_base = parseInt(values[`${stat}_base`])||0; const stat_bonus = parseInt(values[`${stat}_bonus`])||0; const final_total = stat_base + stat_bonus; setAttrs({ [`${stat}`]: final_total }); }); on(`change:m_${stat}_base change:m_${stat}_bonus`, function () { getAttrs([`m_${stat}_base`, `m_${stat}_bonus`], function (values) { const m_stat_base = parseInt(values[`m_${stat}_base`])||0; const m_stat_bonus = parseInt(values[`m_${stat}_bonus`])||0; const final_m_total = m_stat_base + m_stat_bonus; const m_stat_mod = ((Math.floor((final_m_total * (final_m_total - 1))/2) + 1) * Math.ceil(final_m_total/15))||0; setAttrs({ [`m_${stat}`]: final_m_total, [`m_${stat}_mod`]: m_stat_mod }); }); });
1658026589
GiGs
Pro
Sheet Author
API Scripter
Have you tried taking parts out, and seeing if it works? Basically attempting to find what part isnt working properly?
1658027212

Edited 1658027290
GiGs
Pro
Sheet Author
API Scripter
I have made two attempts to express that calculation - try both and see if either give correct answers. const m_stat_mod &nbsp;= (Math.floor((final_m_total * (final_m_total - 1))/2) + 1) * ceil(final_m_total/15); const m_stat_mod = Math.floor((final_m_total * (final_m_total - 1)/2) +1) * Math.ceil(final_m_total/15); If neither of these works, and you can't describe that expression in english, I am going to give up. You need to be able to describe it, for me to know what to do with it. (If you can't describe it in plain English, it has no business being in a RPG either - players need to be able to understand what things on the character sheets mean - and if it is too complex for me to figure out how it's supposed to work, it's too complex for an RPG character sheet).
I can calculate the ATTRIBUTE and m_ATTRIBUTE with this. Seems to work great! const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core.forEach(function (stat) { &nbsp;&nbsp;&nbsp; on(`change:${stat}_base change:${stat}_bonus`, function () { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getAttrs([`${stat}_base`, `${stat}_bonus`], function (values) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const stat_base = parseInt(values[`${stat}_base`])||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const stat_bonus = parseInt(values[`${stat}_bonus`])||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const final_total = stat_base + stat_bonus; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setAttrs({ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [`${stat}`]: final_total &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }); &nbsp;&nbsp;&nbsp; }); &nbsp;&nbsp; &nbsp;on(`change:m_${stat}_base change:m_${stat}_bonus`, function () { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getAttrs([`m_${stat}_base`, `m_${stat}_bonus`], function (values) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const stat_base = parseInt(values[`m_${stat}_base`])||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const stat_bonus = parseInt(values[`m_${stat}_bonus`])||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const final_total = stat_base + stat_bonus; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setAttrs({ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [`m_${stat}`]: final_total &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }); &nbsp;&nbsp;&nbsp; }); }); When I adjust the following, it stops working. I've tried both versions of the code you suggested. const m_stat_mod = (Math.floor((final_m_total * (final_m_total - 1))/2) + 1) * Math.ceil(final_m_total/15); setAttrs({ [`m_${stat}`]: final_m_total, [`m_${stat}_mod`]: m_stat_mod });
1658029401
GiGs
Pro
Sheet Author
API Scripter
I cant tell you what that calculation should be, because I don't know. Here's what I guess it should be: const m_stat_mod = Math.floor((final_m_total - 1) * final_m_total/2 +1) * Math.ceil(final_m_total/15); console.info({final_m_total , m_stat_mod }); I've added a line so you can look at the console to see what it is being calculated as (press F12 while in the campaign, then go to console tab, to see the console). I want to help you, but I just can't, if the calculation is wrong and you can't explain what it should be. Note: Math.floor looks unnecessary, since there's always an even element in the expression, so that calculation is never fractional.
All right, so--I got all that working! However, now the previous sheetworker (the one we worked out to set attribute, m_attribute, and m_attribute_mod from attribute_name). Here's the whole block (sorry for the spam): const stats_core = ['strength', 'dexterity', 'charisma', 'intelligence', 'manipulation', 'wits', 'stamina', 'composure', 'resolve']; stats_core.forEach(function (stat) { on(`change:${stat}_base change:${stat}_bonus`, function () { getAttrs([`${stat}_base`, `${stat}_bonus`], function (values) { const stat_base = parseInt(values[`${stat}_base`])||0; const stat_bonus = parseInt(values[`${stat}_bonus`])||0; const final_total = stat_base + stat_bonus; setAttrs({ [`${stat}`]: final_total }); }); }); on(`change:m_${stat}_base change:m_${stat}_bonus`, function () { getAttrs([`m_${stat}_base`, `m_${stat}_bonus`], function (values) { const stat_base = parseInt(values[`m_${stat}_base`])||0; const stat_bonus = parseInt(values[`m_${stat}_bonus`])||0; const final_total = stat_base + stat_bonus; const stat_mod = (Math.floor((final_total * (final_total - 1))/2) + 1) * Math.ceil(final_total/15); setAttrs({ [`m_${stat}`]: final_total, [`m_${stat}_mod`]: stat_mod }); }); }); }); const sections = ['specials', 'arts', 'spaths', 'ppaths', 'dlores', 'miscs', 'rituals', 'gear']; const stats_all = stats_core.reduce((all, one) =&gt; [...all, one, `m_${one}`, `m_${one}_mod`], []); const stat_changes = stats_all.reduce((changes, stat) =&gt; `${changes} ${stat}`, '').slice(0,-1).toLowercase(); sections.forEach(section =&gt; { const special_attribute_name_string = 'attribute_name'; on(`${stat_changes} change:repeating_${section}:${special_attribute_name_string}`, () =&gt; { getSectionIDs(`repeating_${section}`, id_array =&gt; { const fieldnames = id_array.reduce((all, id) =&gt; [...all, `repeating_${section}_${id}_${special_attribute_name_string}`], []); getAttrs([...fieldnames, ...stats_all], values =&gt; { const output = {}; id_array.forEach(id =&gt; { const special_attribute_name = values[`repeating_${section}_${id}_${special_attribute_name_string}`]; output[`repeating_${section}_${id}_attribute_${section}`] = +values[special_attribute_name] || 0; output[`repeating_${section}_${id}_mattribute_${section}`] = +values[`m_${special_attribute_name}`] || 0; output[`repeating_${section}_${id}_mattribute_mod_${section}`] = +values[`m_${special_attribute_name}_mod`] || 0; }); setAttrs(output); }); }); }); });