Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Help required with attribute max

1552271008

Edited 1552272196
Ray
Pro
Greetings. I have read the wiki and tried to make sense of the forum posts on the subject but I am 110% ignorant regarding how to setup the max value of an attribute on a custom character sheet. I have three attributes that require a max value to be calculated by the sheet: BODY, STUN, and END My sheet calculates and stores the characteristic values for BODY, STUN, and END. I can confirm this because I can select them as values when assigning the red, blue, and green token rings. Here is the code for the creation of the BODY attr: <input class="input40 center" type="number" name="attr_body" disabled="true" value="(round(@{bodyxp}/2)+10)"> However, I do not understand how to create their corresponding attributes for BODY_MAX, STUN_MAX, and END_MAX. Can someone please explain this process for me please? Thanks in advance.
Ok....I have been doing some more reading...&nbsp; <a href="https://app.roll20.net/forum/post/6417606/slug%7D" rel="nofollow">https://app.roll20.net/forum/post/6417606/slug%7D</a> And I have tried to follow the information in that post... This code... Produces this... So, I cannot select body_max because it doesn't exist.... And... This code... Produces this... So, it appears that no "body_max" attribute exists... What am I missing here?
1552278546

Edited 1552279166
vÍnce
Pro
Sheet Author
Not sure if max attributes (seperate input created using "..._max" appended to the primary attributes name) show up on the attributes tab(same list that shows for token linking)... Just enter the main attribute and if there is a corresponding max value it will automatically be used for the second field. example; just use "body" and the second field will automatically use the value for&nbsp; "body_max" if it exists. Also; although roll20 will automatically append "sheet-..." to your class names, it's a good habit to include "sheet-foo" in your html.&nbsp;
1552279641

Edited 1552279730
Ray
Pro
Vince said: Not sure if max attributes (seperate input created using "..._max" appended to the primary attributes name) show up on the attributes tab(same list that shows for token linking)... Just enter the main attribute and if there is a corresponding max value it will automatically be used for the second field. example; just use "body" and the second field will automatically use the value for&nbsp; "body_max" if it exists. Also; although roll20 will automatically append "sheet-..." to your class names, it's a good habit to include "sheet-foo" in your html.&nbsp; Hi Vince. thanks for taking a look. The problem is though, if I create the attribute and call it "body", then there is no corresponding "body_max" in existence... Likewise, if I create the attribute as "body_max", then I cannot select it (or "body") from the dropdown list in the token settings... :'( btw...what is "foo"??&nbsp; :S
1552280677

Edited 1552280836
vÍnce
Pro
Sheet Author
You must create both attributes "attr_body" and "attr_body_max" on your custom sheet. Any attribute that includes "foo _max "&nbsp; ( foo is just a generic/fictional term, like a "widget" that's just used to represent something.&nbsp; In this case "foo" represents any attribute name you might use. ) is reserved by roll20 and will not be seen .&nbsp; It exists, but you will not see it in the list.&nbsp; You can call it directly in a macro ie "@{body|max}" but again, you will not see it on any list.&nbsp;&nbsp; You can choose "body" from the token attribute/link list and "body_max" will automatically be used in the second/max field.
1552284275

Edited 1552284397
Ray
Pro
Vince said: You must create both attributes "attr_body" and "attr_body_max" on your custom sheet. Any attribute that includes "foo _max "&nbsp; ( foo is just a generic/fictional term, like a "widget" that's just used to represent something.&nbsp; In this case "foo" represents any attribute name you might use. ) is reserved by roll20 and will not be seen .&nbsp; It exists, but you will not see it in the list.&nbsp; You can call it directly in a macro ie "@{body|max}" but again, you will not see it on any list.&nbsp;&nbsp; You can choose "body" from the token attribute/link list and "body_max" will automatically be used in the second/max field. Ahhh...that makes sense...so I have to create two attributes...one called "body" and the other called "body_max", and the system will automatically link them because their name is the same? Ok...next question... So I have created the "body_max" attribute which shows up on the character sheet. The corresponding "body" attribute is something I don't want to be seen on the sheet...after all, it is the variable component which will change on the token ring. How do I create the "body" attribute so that it does not appear on the actual sheet? Here is my current code: &lt;input class="input40 center" type="number" name="attr_body_max" disabled="true" value="(round(@{bodyxp}/2)+10)"&gt; Do I add another line that looks something like this?: &lt;input type="number" name="attr_body" value="(round(@{bodyxp}/2)+10)"&gt; or like this? &lt;input type="number" name="attr_body" value="@{body_max}"&gt;
1552285032
vÍnce
Pro
Sheet Author
If you don't want an attribute to be seen on the sheet or show up on the attributes list, use type="hidden".
1552305938

Edited 1552306028
GiGs
Pro
Sheet Author
API Scripter
To do what you want, the way you want, you;d need three stats: &lt;input name="attr_body" type="hidden" value="10" &gt; &lt;input name="attr_body_max" type="number" value="round(@{bodyxp}/2)+10" disabled&gt; &lt;input name="attr_bodyxp" type="number" value="0" &gt; With this method, body wont be on the character sheet (it's type=hidden) but will appear in the token's drop down, and when you select it, the current and max will be automatically filled in. The BIG problem with this approach is you can't set the base value of body - it will always have to be filled in manually. If you try to set it like this: &lt;input name="attr_body" type="number" value="round(@{bodyxp}/2)+10" disabled&gt; You cant actually change its value on the token. It will be reset to the calculated value every time. That's what the disabled part of the attribute means. The best way to do this is more complicated: using a sheet worker. You'd set up your attributes in a similar way: &lt;input name="attr_body" type="hidden" value="10" &gt; &lt;input name="attr_body_max" type="number" value="10" readonly&gt; &lt;input name="attr_bodyxp" type="number" value="0" &gt; And then you'd have a script block at the end of your html file, to hold your sheet workers, which looks like this: &lt;script type="text/worker"&gt; &lt;/script&gt; All sheet workers you create go between those two script lines. Here's one for calculating body: on('change:bodyxp', function() { &nbsp; &nbsp; getAttrs(['bodyxp'], function(v) { &nbsp; &nbsp; &nbsp; &nbsp; const settings = {}; &nbsp; &nbsp; &nbsp; &nbsp; const body = Math.round((v.bodyxp*1||0)/2)+10; &nbsp; &nbsp; &nbsp; &nbsp; settings.body = body; &nbsp; &nbsp; &nbsp; &nbsp; settings.body_max = body; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs(settings); &nbsp; &nbsp; }); }); It looks complicated but its easy to use. you can copy and paste it for other things, just changing the attribute names in each line, and the calculation on the const body = Math.round line.
1552306532
GiGs
Pro
Sheet Author
API Scripter
Unrelated to the topic: Vince said: Also; although roll20 will automatically append "sheet-..." to your class names, it's a good habit to include "sheet-foo" in your html.&nbsp; Actually i think the sheet guidelines say the opposite: don't include sheet- in your html, because its redundant, but do include it in your css. This does make things a bit more complicated especially for the less experienced, so use whichever is most comfortable.
1552306757
GiGs
Pro
Sheet Author
API Scripter
Ray, it looks like you are using hero system/champions. Which edition are you using? If you want to go the sheet worker approach, I can write you a sheet worker set that handles creating all the attributes current and max scores. I dont own the latest edition, so you'd need to give me a full list of their names, calculations, and costs. For earlier editions I don't need that.&nbsp;
1552328267

Edited 1552328901
vÍnce
Pro
Sheet Author
GiGs said: Unrelated to the topic: Vince said: Also; although roll20 will automatically append "sheet-..." to your class names, it's a good habit to include "sheet-foo" in your html.&nbsp; Actually i think the sheet guidelines say the opposite: don't include sheet- in your html, because its redundant, but do include it in your css. This does make things a bit more complicated especially for the less experienced, so use whichever is most comfortable. Thanks GiGs. I didn't know that (amongst many other things...)&nbsp; I've always included the "sheet-..." in my html.&nbsp; I do a lot of "tinkering" within the browser's element/css inspector and it's handy to copy paste my changes between the sheet's css file and browser when they are consistent.&nbsp; Truthfully, it's just become automatic for me to include it.&nbsp; I suppose it's just as superfluous as including ".charsheet " within one's css.&nbsp; I've seen lot's of that in other author's code as well.
1552332204
GiGs
Pro
Sheet Author
API Scripter
I'vebeen pretty inconsistent about it, and the amount of times i forget to include in the css files and spend some time trying to figure out why things aren't working is embarrassing :) having it in the html does make that copy pasting easier. What I've started to do is work on it with the 'sheet-' in place during sheet design, for easy of copy/paste, then at the end do a mass find/replace, to remove all 'sheet-' elements from the html file. But for a continually maintained sheet thats not as useful.
1552382462

Edited 1552382706
Ray
Pro
GiGs said: Ray, it looks like you are using hero system/champions. Which edition are you using? If you want to go the sheet worker approach, I can write you a sheet worker set that handles creating all the attributes current and max scores. I dont own the latest edition, so you'd need to give me a full list of their names, calculations, and costs. For earlier editions I don't need that.&nbsp; Hi GiGs...well spotted. I am using Hero System 4th edition. The 3 x characteristics I am using which require a solution to this problem are "body", "end", and "stun" So I need a "body_max", "end_max", and "stun_max" to go with them. I have created the following code: This code defines the attibutes "body_max" and "bodyxp" &lt;div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field40 right"&gt;BODY&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field10"&gt;&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field45 center"&gt;10&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field45 center"&gt;x2&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;input class="input30 center" type="number" name="attr_bodyxp" value="0"&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field45 center"&gt;20&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field10"&gt;&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;input class="input40 center" type="number" name="attr_body_max" disabled="true" value="(round(@{bodyxp}/2)+10)"&gt; &nbsp; &nbsp; &nbsp; &lt;input name="attr_body" value="@{body_max}"&gt;&nbsp; &nbsp; &nbsp; &nbsp; *** Note: I need this line to be hidden on the character sheet *** &lt;/div&gt; So...the formula for "body" and "body_max" is ((bodyxp/2)+10) Note that "body" will be the variable value used in the token ring, and that both "body" and "bodymax" need to increase if the "bodyxp" increases. This code defines "end_max" and "endxp"... &lt;div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field40 right"&gt;END&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field10"&gt;&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field80b center"&gt;2x(CON)&lt;/div&gt; &lt;div class="field45 center"&gt;x0.5&lt;/div&gt; &lt;input class="input30 center" type="number" name="attr_endxp" value="0"&gt; &lt;div class="field30 center"&gt;50&lt;/div&gt; &nbsp; &nbsp; &nbsp; &nbsp; &lt;div class="field10"&gt;&lt;/div&gt; &lt;input class="input40 center" type="number" name="attr_end_max" disabled="true" value="@{con}*2+@{endxp}*2"&gt; &nbsp; &nbsp; &nbsp; &nbsp; &lt;input name="attr_end" value="@{end_max}"&gt;&nbsp; &nbsp; &nbsp; &nbsp; *** Note: I need this line to be hidden on the character sheet *** &lt;/div&gt; likewise...the formula for "end" and "end_max" is ((con*2)+(endxp*2)) again, the "end" will be the variable value used in the token ring, and both "end" and "endmax" need to increase if the "endxp" increases. and lastly, the "stun_max" and "stunxp" are as follows: &lt;div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field40 right"&gt;STUN&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field10"&gt;&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field80b center"&gt;BODY+(STR/2)+(CON/2)&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field45 center"&gt;x1&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;input class="input30 center" type="number" name="attr_stunxp" value="0"&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field30 center"&gt;50&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;div class="field10"&gt;&lt;/div&gt; &nbsp; &nbsp; &nbsp; &lt;input class="input40 center" type="number" name="attr_stun_max" disabled="true" value="@{body}+round(@{str}/2)+round(@{con}/2)+@{stunxp}"&gt; &nbsp; &nbsp; &nbsp; &lt;input name="attr_stun" value="@{stun_max}"&gt;&nbsp; &nbsp; &nbsp; &nbsp; *** Note: I need this line to be hidden on the character sheet *** &lt;/div&gt; so...the formula for "stun" and "stun_max" is (body+(str/2)+(con/2)) again, the "stun" will be the variable value used in the token ring, and both "stun" and "stun_max" need to increase if the "stunxp" increases. I would love to get a sheetworker script that will handle this, but I know nothing about sheetworker except the basic understanding of it's function, though the code is completely foreign to me. Regards&nbsp; &nbsp;:)
GiGs said: Unrelated to the topic: Vince said: Also; although roll20 will automatically append "sheet-..." to your class names, it's a good habit to include "sheet-foo" in your html.&nbsp; Actually i think the sheet guidelines say the opposite : don't include sheet- in your html, because its redundant, but do include it in your css. This does make things a bit more complicated especially for the less experienced, so use whichever is most comfortable. Yeah, they do...that's why I leave the "sheet-" out of my html. I had no idea about this and was leaving it in my code until I read that in the sheet guidelines :/
GiGs said: To do what you want, the way you want, you;d need three stats: &lt;input name="attr_body" type="hidden" value="10" &gt; &lt;input name="attr_body_max" type="number" value="round(@{bodyxp}/2)+10" disabled&gt; &lt;input name="attr_bodyxp" type="number" value="0" &gt; With this method, body wont be on the character sheet (it's type=hidden) but will appear in the token's drop down, and when you select it, the current and max will be automatically filled in. The BIG problem with this approach is you can't set the base value of body - it will always have to be filled in manually. If you try to set it like this: &lt;input name="attr_body" type="number" value="round(@{bodyxp}/2)+10" disabled&gt; You cant actually change its value on the token. It will be reset to the calculated value every time. That's what the disabled part of the attribute means. It looks complicated but its easy to use. you can copy and paste it for other things, just changing the attribute names in each line, and the calculation on the const body = Math.round line. Ok...I understand what you are saying above, I have an IT background, although not in HTML, but I get where you are coming from. I agree...not having the value of "body" change dynamically on the token is a huge issue... Since you are familiar with the rule set, you would know that essentially the only inputs from the player on the sheet are "bodyxp", "endxp", and "stunxp". As those values increase, a corresponding re-calculation of their "foo" and "foo_max" are essential, so a sheetworker that automatically resets their values on change of their "fooxp" definitely seems the way to go. The problem of course is the other two characteristics of "end" and "stun". The value of "end" is affected by changes in both "con" and "endxp", and likewise "stun" is affected by changes in "body", "str", "con", and "stunxp".
1552403860

Edited 1552405301
GiGs
Pro
Sheet Author
API Scripter
Okay to do what you need with sheet workers, here's what your inputs should look like for the relevant 5 attributes: &lt;input name="attr_str" type="hidden" value="10" &gt; &lt;input name="attr_str_max" type="number" value="10" readonly&gt; &lt;input name="attr_strxp" type="number" value="0" &gt; &lt;input name="attr_con" type="hidden" value="10" &gt; &lt;input name="attr_con_max" type="number" value="10" readonly&gt; &lt;input name="attr_conxp" type="number" value="0" &gt; &lt;input name="attr_body" type="hidden" value="10" &gt; &lt;input name="attr_body_max" type="number" value="10" readonly&gt; &lt;input name="attr_bodyxp" type="number" value="0" &gt; &lt;input name="attr_end" type="hidden" value="10" &gt; &lt;input name="attr_end_max" type="number" value="10" readonly&gt; &lt;input name="attr_endxp" type="number" value="0" &gt; &lt;input name="attr_stun" type="hidden" value="10" &gt; &lt;input name="attr_stun_max" type="number" value="10" readonly&gt; &lt;input name="attr_stunxp" type="number" value="0" &gt; Notice that the _max stat is readonly (not disabled), and the current score has type="hidden" (that means exactly what it says - roll20 wont display it on the character sheet, no CSS needed). The hidden attributes dont need to be next to the max versions - you can move them elsewhere on the sheet. Maybe even remove the hidden part and display them under a Current Health box you create to display them. This is a good idea IMO. Then you need the script block to calculate the scores. VERY IMPORTANT: all attributes used in sheet workers must be entered manually, or calculated by other sheet workers. You cannot use autocalc fields (ones calculated with value="@{str_max}+@{con_max}).&nbsp; I'll give a brief explanation after: &lt;script type="text/worker"&gt; // BODY CALCULATION on('change:bodyxp', function() { getAttrs(['bodyxp'], function(values) { const int = stat =&gt; parseInt(values[stat],10)||0; const body_points = int('bodyxp'); const score = Math.floor(body_points/2) + 10; setAttrs({ stun: score, stun_max: score }); }); }); // END CALCULATION on('change:con_max change:endxp', function() { getAttrs(['con_max', 'endxp'], function(values) { const int = stat =&gt; parseInt(values[stat],10)||0; const end_points = int('endxp'); const con = int('con_max'); const score = con *2 + end_points; setAttrs({ stun: score, stun_max: score }); }); }); // STUN CALCULATION on('change:body_max change:con_max change:str_max change:stunxp', function() { getAttrs(['body_max', 'con_max', 'str_max', 'stunxp'], function(values) { const int = stat =&gt; parseInt(values[stat],10)||0; const stun_points = int('stunxp'); const body = int('body_max'); const con = int('con_max'); const str = int('str_max'); const score = Math.ceil(str/2) + Math.ceil(con/2) + body + stun_points; setAttrs({ stun: score, stun_max: score }); }); }); &lt;/script&gt; A Sheet Worker Explained I'll go through the first sheet worker and explain how it works (and to any more experienced coders, yes, these sheet workers could be written more efficiently, I chose to write them in a way that makes them hopefully easier to understand). The first line of the worker: &nbsp;&nbsp;&nbsp;&nbsp;on('change:body_xp', function() { sets up a watcher. It watches the character sheet for changes, and when the stat named bodyxp is altered, the rest of the sheet worker happens. The second line: getAttrs(['bodyxp'], function(values) { this has a bunch of confusing elements. All you need to grasp is this: sheet workers don't have any access to the rest of the character sheet. They dont know what attributes are, or what their values are. So you need to supply it the attributes and scores you need for the sheet worker to function. This line basically says: look at the character sheet, find the attribute named body_xp, and its name and value in a data object called values. Once this is done, later in the sheet worker, you can access that attribute by calling it from values, as you'll see in a moment. const int = stat =&gt; parseInt(values[stat],10)||0; This line might not look like it, but it's a function. It could also be written as function int(stat) { &nbsp; &nbsp;var int = parseInt(values[stat], 10)||0; &nbsp; &nbsp;return int; } What it does is: you send it the name of an attribute, and it looks in the values object you just created to get the attribute's value. It then converts it into an integer.&nbsp; The ||0 at the end says, if its not a number, set it to 0. This handles bad inputs. That's a lot to process, but like most of sheet workers, you dont need to know how it works. So, onto const body_points = int('bodyxp'); This creates the body_points variable, using the above function. So after all this, you finally know how many points have been spent on body, and can calculate the score. const score = Math.floor(body_points/2) + 10; setAttrs({ stun: score, stun_max: score Here we calculate the score, and then update it on the character sheet. setAttrs is the function which updates attributes on the character sheet. Here we update the stun and stun_max attributes. }); }); }); Finally we need to close the 3 functions - setAttrs, getAttrs, and the on change function. Once you are familiar with a sheet worker, you don't really need to change much. it's just a matter of putting the correct attribute names in the change line, the getAttrs line, the int call to get the attribute values, and the setAttrs functions. The rest should be basic arithmetic. As you can see with the second and third workers, dealing with more than one attribute is the same process.
1552404029
GiGs
Pro
Sheet Author
API Scripter
Some things that occurred me: You say you want the body (for example) stat hidden, and just show the body. I'd recommend having a place on the character sheet where you do show the current body, stun, and end - for current health display purposes. People don't always have access to their tokens (when the GM is switching maps for instance), and being able to directly adjust current stats on the sheet is useful. Is your campaign a hero or superhero level campaign? Will you need to implement the attribute maxima rules (double cost over 20 for str, etc)? I can add the changes needed for that. Does your campaign use Drain/Aid type effects, where stats other than those three can be altered? If you want to deal with that, I can post a more advanced version of the scripts above that covers all the stats and allows for other temporary adjustments.
1552418900

Edited 1552419074
Ray
Pro
GiGs said: Some things that occurred me: You say you want the body (for example) stat hidden, and just show the body. I'd recommend having a place on the character sheet where you do show the current body, stun, and end - for current health display purposes. People don't always have access to their tokens (when the GM is switching maps for instance), and being able to directly adjust current stats on the sheet is useful. Is your campaign a hero or superhero level campaign? Will you need to implement the attribute maxima rules (double cost over 20 for str, etc)? I can add the changes needed for that. Does your campaign use Drain/Aid type effects, where stats other than those three can be altered? If you want to deal with that, I can post a more advanced version of the scripts above that covers all the stats and allows for other temporary adjustments. Hi GiGs....thanks so much for your help with this, I am really grateful. Your explanation of the sheetworker syntax was really helpful, and even though I had figured out the meaning of most of the code from other sheetworkers I have looked at, some of it was still unclear to me...until now&nbsp; ;) My campaign is a Hero level game. Normal human maxima rules do apply, so I see what you are getting at with that question. Also, yes Drain and Aid do both apply as magical effects, so point taken, again, it would be best to have fields available as variables on the character sheet for temporary adjustment of all the characteristics. *nod* Note: I had actually intended to just manually handle these kinds of issues, as I am new to the HTML and CSS coding and haven't yet the skills advanced enough to automate these functions...but I am getting there ;) Any code you can offer would be gratefully accepted. I would also be willing to give you a copy of the HTML and CSS so you could take a look at the working sheet and give some feedback on it? The sheet is specific to my game only, so even though I have designed it for the Hero 4th edition rules, I have no intention of releasing it for public consumption. Regards P.S...I just had a thought...I also need to take into account effects like DRAIN vs. CON would have a flow on effect to the calculation for STUN...
1552420422
GiGs
Pro
Sheet Author
API Scripter
I'd be happy to take a look at your html/css, though I might not be able to help that much. You can post them to pastebin and post a link here, or PM me with a link to a zip file. IIRC drain/aid effects automatically have the Do Not Affect Figured Characteristics limitation, so they wouldn't flow on to the derived stats.&nbsp; I'll post an updated sheetworker set later when I have time.&nbsp;
1552444036

Edited 1552500242
GiGs
Pro
Sheet Author
API Scripter
This turned out to be a little tricky but I've done it. I think i've caught all the bugs. So, for this to work you need a couple extra inputs for each stat, but you can hide them or move them somewhere else. First, you need a checkbox input: &lt;input name="attr_stat_maximum" type="checkbox" value="1" &gt; When this is checked, the sheet will use the attribute maximums and halve stats above them. When unchecked, it wont. You can put this input in a configuration section, or wherever you want. Then for the attributes: here's what the strength group looks like &nbsp;&nbsp;&nbsp;&nbsp;&lt;input&nbsp; name="attr_str" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_str_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span&gt;STR&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_adjust" type="number" value="0" &gt; str is the current value - you could make it hidden, but as discussed earlier, if you are using aids, drains, and similar effect its a good idea to show it so players know their current status. I set it as readonly because players dont need to adjust this directly. str_max - that attribute max. you need this in case you take damage to the current score (like stun, end, body, or drains to other stats).&nbsp; str_ltd - this is a special stat that should always be hidden. It's used for calculating derived stats. Derived stats don't have this attribute. str_maxima - this is the stat maximum. usually 20 for primary stats. But if other races exist, sometimes it will vary. If your races all have maxima 20, or you aren't using the maxima rules, you can set this to type="hidden" and never worry about it. str_xp: the points invested in the stat. I added an _ to the name, to match all the other stat names. str_bonus: a bonus (or penalty) you add to a stat, which does &nbsp;affect derived stats. Derived stats don't have this attribute. str_adjust: a bonus or penalty applied to a stat that does not &nbsp;affect derived stats. The question of how to handle derived stats, and abilities that do or dont affect them made building the sheet workers a bit trickier than anticipated. _bonus and _adjust dont need to be next to the attributes, but its more convenient if they are. You could have them elsewhere on the sheet, a place for&nbsp; Most stat affecting things act like adjustment powers, and dont affect derived attributes. That's what the _adjust attribute is for. But sometimes players get benefits that do affect the derived stats, that's what the _bonus attribute is for. Players might have spells, or a magic item, or a racial power that has advantages or limitations applied, so its not bought like a normal stat, but you still need to include its effect. Thats what the _bonus attribute is for. I'll post the full set of stats I used for testing in the next post, for reference.&nbsp; So, once you have the stats set up, its time to add the sheet workers. Remember these go inspide the &lt;script&gt; block. const stats_core = { &nbsp; &nbsp; str: { cost: 1, base: 10, },&nbsp; &nbsp; &nbsp; con: { cost: 2, base: 10, }, &nbsp; &nbsp; body: { cost: 2, base: 10}, &nbsp; &nbsp; dex: { cost: 3, base: 10}, &nbsp; &nbsp; int: { cost: 1, base: 10}, &nbsp; &nbsp; ego: { cost: 2, base: 10}, &nbsp; &nbsp; pre: { cost: 1, base: 10}, &nbsp; &nbsp; com: { cost: 0.5, base: 10}, }; const stats_derived = { &nbsp; &nbsp; pd: { cost: 1, base: 0, derived: {str: 0.2},}, &nbsp; &nbsp; ed: { cost: 1, base: 0, derived: {con: 0.2},}, &nbsp; &nbsp; spd: { cost: 1, base: 10, derived: {dex: 1}}, &nbsp; &nbsp; rec: { cost: 2, base: 0, derived: {str: 0.2, con: 0.2},}, &nbsp; &nbsp; end: { cost: 0.5, base: 0, derived: {con: 2},}, &nbsp; &nbsp; stun: { cost: 1, base: 0, derived: {str: 0.5, con: 0.5, body: 1},}, }; Object.keys(stats_core).forEach(stat =&gt; { &nbsp; &nbsp; const stat_properties = stats_core[stat]; &nbsp; &nbsp; let stat_array = [`${stat}_xp`, `${stat}_bonus`, `${stat}_adjust`, `${stat}_maxima`, `stat_maximum` ]; &nbsp; &nbsp; on(stat_array.reduce((changes, current) =&gt; changes + `change:${current} `, ''), () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; getAttrs(stat_array, values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const int = stat =&gt; parseInt(values[stat],10)||0,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings = {}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let max_stat = int(stat + '_maxima'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score_bonus = stat_properties.base + Math.floor((int(stat + '_xp') + int(stat + '_bonus') )/stat_properties.cost), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score_adjust = stat_properties.base + Math.floor((int(stat + '_xp') + int(stat + '_bonus') + int(stat + '_adjust'))/stat_properties.cost); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(int('stat_maximum') &amp;&amp; score_bonus &gt; max_stat) score_bonus = Math.round((max_stat + score_bonus)/2); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(int('stat_maximum') &amp;&amp; score_adjust &gt; max_stat) score_adjust = Math.round((max_stat + score_adjust)/2); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings[stat] = score_adjust; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings[`${stat}_max`] = score_adjust; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings[`${stat}_ltd`] = score_bonus; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs(settings); &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; }); Object.keys(stats_derived).forEach(stat =&gt; { &nbsp; &nbsp; const stat_properties = stats_derived[stat]; &nbsp; &nbsp; let derived_array = Object.keys(stat_properties.derived); &nbsp; &nbsp; let full_array = derived_array.map(stat_temp =&gt; stat_temp + '_ltd'); &nbsp; &nbsp; full_array.push(`${stat}_xp`, `${stat}_adjust`, `${stat}_maxima`, `stat_maximum` ); &nbsp; &nbsp; on(full_array.reduce((changes, current) =&gt; changes + `change:${current} `, ''), () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; getAttrs(full_array, values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const int = stat =&gt; parseInt(values[stat],10)||0,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings = {}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let max_stat = int(stat + '_maxima'), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; score = derived_array.reduce((total, temp) =&gt;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; total + Math.round(int(temp + '_ltd') * stat_properties.derived[temp]),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stat_properties.base + Math.floor((int(stat + '_xp') + int(stat + '_adjust'))/stat_properties.cost)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(stat === 'spd') max_stat *= 10; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(int('stat_maximum') &amp;&amp; score &gt; max_stat) score = Math.round((max_stat + score)/2); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(stat === 'spd') score = score/ 10; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings[stat] = score; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; settings[`${stat}_max`] = score; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs(settings); &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; }); Add this code to your script block, and it will handle all the stats, updating their values automatically. If you have any questions about how it works, ask away, but you should be able to just drop it in to your sheet and forget about it. Also, remove the sheet workers I gave earlier - this replaces them.
1552444561
GiGs
Pro
Sheet Author
API Scripter
This is the complete list of attributes I used: &lt;div class="statistics"&gt; &nbsp; &nbsp; &lt;span &gt;TEMP&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;SCORE&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;STAT&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;MAX&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;COST&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;PTS&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;BONUS&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;span &gt;ADJUST&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_str_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span class="sheet-stats"&gt;STR&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_str_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_dex" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_dex_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_dex_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;DEX&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_dex_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x3&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_dex_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_dex_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_dex_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_con" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_con_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_con_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;CON&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_con_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x2&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_con_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_con_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_con_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_body" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_body_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_body_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;BODY&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_body_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x2&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_body_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_body_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_body_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_int" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_int_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_int_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;INT&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_int_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_int_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_int_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_int_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ego" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ego_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_ego_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;EGO&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ego_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x2&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ego_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ego_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ego_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pre" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pre_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_pre_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;PRE&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pre_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pre_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pre_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pre_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_com" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_com_max" type="number" value="10" readonly&gt; &nbsp; &nbsp; &lt;input name="attr_com_ltd" type="hidden" value="10" readonly&gt; &nbsp; &nbsp; &lt;span &gt;COM&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_com_maxima" type="number" value="20" &gt; &nbsp; &nbsp; &lt;span &gt;x1/2&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_com_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_com_bonus" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_com_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pd" type="number" value="2" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pd_max" type="number" value="2" readonly&gt; &nbsp; &nbsp; &lt;span &gt;PD&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pd_maxima" type="number" value="8" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pd_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;span &gt;&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_pd_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ed" type="number" value="2" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ed_max" type="number" value="2" readonly&gt; &nbsp; &nbsp; &lt;span &gt;ED&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ed_maxima" type="number" value="8" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ed_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;span &gt;&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_ed_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_spd" type="number" value="2" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_spd_max" type="number" value="2" readonly&gt; &nbsp; &nbsp; &lt;span &gt;SPD&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_spd_maxima" type="number" value="4" &gt; &nbsp; &nbsp; &lt;span &gt;x10&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_spd_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;span &gt;&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_spd_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_rec" type="number" value="4" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_rec_max" type="number" value="4" readonly&gt; &nbsp; &nbsp; &lt;span &gt;REC&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_rec_maxima" type="number" value="10" &gt; &nbsp; &nbsp; &lt;span &gt;x2&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_rec_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;span &gt;&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_rec_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_end" type="number" value="20" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_end_max" type="number" value="20" readonly&gt; &nbsp; &nbsp; &lt;span &gt;END&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_end_maxima" type="number" value="50" &gt; &nbsp; &nbsp; &lt;span &gt;x1/2&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_end_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;span &gt;&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_end_adjust" type="number" value="0" &gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_stun" type="number" value="20" readonly&gt; &nbsp; &nbsp; &lt;input&nbsp; name="attr_stun_max" type="number" value="0" readonly&gt; &nbsp; &nbsp; &lt;span &gt;STUN&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_stun_maxima" type="number" value="50" &gt; &nbsp; &nbsp; &lt;span &gt;x1&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_stun_xp" type="number" value="0" &gt; &nbsp; &nbsp; &lt;span &gt;&lt;/span&gt;&nbsp; &nbsp; &nbsp; &lt;input&nbsp; name="attr_stun_adjust" type="number" value="0" &gt; &lt;/div&gt; And I used this css to display them as a table div.sheet-statistics { &nbsp; display: grid; &nbsp; grid-template-columns: repeat(8, 50px); } So it looked like this If you look through the stats, you'll see the primary stats have the _ltd and _bonus attributes, and the derived stats dont have those. The rest are the same. I'd personally probably keep the adjust column there, but move the Bonus one elsewhere: wherever you track powers, special abilities, talents, I'd probably put a place for buying stat bonuses.
1552455112

Edited 1552455120
vÍnce
Pro
Sheet Author
I am so glad GiGs got involved.&nbsp; I had know idea were this was actually heading.&nbsp; lol
Vince said: I am so glad GiGs got involved.&nbsp; I had know idea were this was actually heading.&nbsp; lol LOL...no worries Vince...I appreciate your help&nbsp; :)
GiGs said: I'd be happy to take a look at your html/css, though I might not be able to help that much. You can post them to pastebin and post a link here, or PM me with a link to a zip file. IIRC drain/aid effects automatically have the Do Not Affect Figured Characteristics limitation, so they wouldn't flow on to the derived stats.&nbsp; I'll post an updated sheetworker set later when I have time.&nbsp; Holy crap Gigs...I think my brain just exploded LOL *twitch, twitch*. I'm gonna have to take some time looking at this code you've written....I will come back to you in a few days... Thanks so much....totally in your debt. Regards :D
1552489153
GiGs
Pro
Sheet Author
API Scripter
hehe, thanks both of you. Ray, I should have mentioned, the technique I used here is built on the stuff in the Universal Sheet Workers page on the wiki , and it includes a simpler to understand version of the stuff I did here.
1552490336
GiGs
Pro
Sheet Author
API Scripter
Also Ray, since you're playing hero system, and are Pro, you might find this damage roll script useful: <a href="https://app.roll20.net/forum/post/6009891/script-champions-slash-hero-system-damage-roller" rel="nofollow">https://app.roll20.net/forum/post/6009891/script-champions-slash-hero-system-damage-roller</a>
1552503131
GiGs
Pro
Sheet Author
API Scripter
One more update: here's a sheet worker to total up the points spent in the _xp column const stats = Object.keys(stats_core).concat(Object.keys(stats_derived)); on(stats.reduce((changes, current) =&gt; changes + `change:${current}_xp `, ''), () =&gt; { getAttrs(stats.map(stat =&gt; `${stat}_xp`), values =&gt; { const int = stat =&gt; parseInt(values[stat],10)||0, points = stats.reduce((total, current) =&gt; total + int(current + '_xp'),0); setAttrs({ stat_points: points }); }); }); This assumes you have the code above in the sheet. Add this to your script block. You just need an input to store them in, something like &lt;span&gt;Attribute Points&lt;/span&gt; &lt;input class="sheetmaximum" name="attr_stat_points" type="number" value="0" &gt;