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

How to use sheet workers combined with autocalc fields ?

Hello. I have some computed value I would like to display without the ugly arrows. Following this advice (<a href="https://app.roll20.net/forum/post/6404362/question-auto-calc-span" rel="nofollow">https://app.roll20.net/forum/post/6404362/question-auto-calc-span</a>), I decided I would try to use a sheet worker to compute my value and put it into a span instead of doing it the dirty way. So I tried with a very simple example and it worked. However in my actual sheet, I use a select/option field to edit some of my attributes. Each option is a different autocalc formula. And then I learned that sheet workers can't process autocalc attributes natively. But then I also found out about this script :<a href="https://github.com/Lithl/lithl-snippets/tree/master/sheetworker-autocalc" rel="nofollow">https://github.com/Lithl/lithl-snippets/tree/master/sheetworker-autocalc</a>. However I have no idea how to use this. I have not found a complete example anywhere. What should i do with the .js file ? I tried copying the code at the end of my huge html file, inside the &lt;script type="text/worker"&gt; section, but it did not work. Since I have never done javascript/css/html before, I guess I'm missing something obvious, but what ?
1579707622
GiGs
Pro
Sheet Author
API Scripter
The best thing to do if you have autocalc fields and sheet worker scripts that depend on them, is to rewrite all dependent fields as sheet workers. If you show your autocalc fields, we can show you how to rewrite them as sheet workers.
Ok, here it goes (I only copied the relevant parts). There's a fair number of steps, so I hope it will not be too painful. FOR/DEX/INT, etc... all behave similiarly. NIVEAU would simply be "Level" for english-speakers, and is a simple attribute not dependant on autocalc. &lt;td class="sheet-boxinput"&gt; &lt;input type="number" name="attr_FORCE_BASE" title="@{FORCE_BASE}" value="10" min="0" /&gt; &lt;/td&gt; &lt;td class="sheet-boxinputlight"&gt; &lt;select class="sheet-carac" name="attr_FORCE_LETTRE" style="width:40px;" title="@{FORCE_LETTRE}" size="1"&gt; &lt;option value="120"&gt;S+&lt;/option&gt; &lt;option value="105"&gt;S&lt;/option&gt; &lt;option value="95"&gt;A+&lt;/option&gt; &lt;option value="80"&gt;A&lt;/option&gt; &lt;option value="70"&gt;B+&lt;/option&gt; &lt;option value="60"&gt;B&lt;/option&gt; &lt;option value="50" selected&gt;C+&lt;/option&gt; &lt;option value="40"&gt;C&lt;/option&gt; &lt;option value="30"&gt;D+&lt;/option&gt; &lt;option value="25"&gt;D&lt;/option&gt; &lt;option value="20"&gt;F+&lt;/option&gt; &lt;option value="15"&gt;F&lt;/option&gt; &lt;/select&gt; &lt;/td&gt; &lt;td class="sheet-boxinputlight"&gt; &lt;input type="number" name="attr_FORCE_DIVERS" title="@{FORCE_DIVERS}" value="0" step="5" /&gt; &lt;/td&gt; &lt;td class="sheet-boxinputlight"&gt; &lt;input type="number" name="attr_FORCE_CODE" value="(@{FORCE_LETTRE} + @{FORCE_DIVERS})" title="@{FORCE_CODE} Scaling total" disabled /&gt; &lt;/td&gt; &lt;td class="sheet-boxinputlight"&gt; &lt;input type="number" name="attr_FORCE_TOT" title="@{FORCE_TOT}" style="width:60px;" value="round(@{FORCE_BASE}+(@{NIVEAU}*@{FORCE_CODE})/100)" disabled /&gt; &lt;/td&gt; &lt;td class="sheet-boxinputlight"&gt; &lt;input type="number" name="attr_FORCE" title="@{FORCE}" value="(@{FORCE_TOT})" disabled /&gt; &lt;/td&gt; &lt;td class="sheet-boxinputlight"&gt; &lt;input type="number" name="attr_FOR" title="@{FOR}" value="floor(@{FORCE}/2 - 5)" disabled /&gt; &lt;/td&gt; &lt;td class="sheet-boxinput"&gt; &lt;input type="number" name="attr_BARPHY_CLASSE_POINTS" value="0" title="@{BARPHY_CLASSE_POINTS} Score de base" /&gt; &lt;/td&gt; &lt;td class="sheet-boxinput" style="padding: 2px 0; text-align: center; width:120px;"&gt; &lt;select class="sheet-carac-cust" name="attr_BARPHY_CARAC1" style="width:37px;" title="@{BARPHY_CARAC1}" size="1"&gt; &lt;option value="0"&gt;-&lt;/option&gt; &lt;option value="(@{FOR}*2)"&gt;FOR&lt;/option&gt; &lt;option value="(@{DEX}*2)"&gt;DEX&lt;/option&gt; &lt;option value="(@{CON}*2)"&gt;CON&lt;/option&gt; &lt;option value="(@{INT}*2)"&gt;INT&lt;/option&gt; &lt;option value="(@{SAG}*2)"&gt;SAG&lt;/option&gt; &lt;option value="(@{CHA}*2)"&gt;CHA&lt;/option&gt; &lt;option value="(@{NIVEAU}/2)" selected&gt;NIV+&lt;/option&gt; &lt;option value="(@{NIVEAU}/4)" &gt;NIV&lt;/option&gt; &lt;/select&gt;, &lt;select class="sheet-carac-cust" name="attr_BARPHY_CARAC2" style="width:37px;" title="@{BARPHY_CARAC2}" size="1"&gt; &lt;option value="0" selected&gt;-&lt;/option&gt; &lt;option value="(@{FOR}*2)"&gt;FOR&lt;/option&gt; &lt;option value="(@{DEX}*2)"&gt;DEX&lt;/option&gt; &lt;option value="(@{CON}*2)"&gt;CON&lt;/option&gt; &lt;option value="(@{INT}*2)"&gt;INT&lt;/option&gt; &lt;option value="(@{SAG}*2)"&gt;SAG&lt;/option&gt; &lt;option value="(@{CHA}*2)"&gt;CHA&lt;/option&gt; &lt;/select&gt;, &lt;select class="sheet-carac-cust" name="attr_BARPHY_CARAC3" style="width:37px;" title="@{BARPHY_CARAC3}" size="1"&gt; &lt;option value="0" selected&gt;-&lt;/option&gt; &lt;option value="(@{FOR}*2)"&gt;FOR&lt;/option&gt; &lt;option value="(@{DEX}*2)"&gt;DEX&lt;/option&gt; &lt;option value="(@{CON}*2)"&gt;CON&lt;/option&gt; &lt;option value="(@{INT}*2)"&gt;INT&lt;/option&gt; &lt;option value="(@{SAG}*2)"&gt;SAG&lt;/option&gt; &lt;option value="(@{CHA}*2)"&gt;CHA&lt;/option&gt; &lt;/select&gt; &lt;/td&gt; &lt;td class="sheet-boxinput"&gt; &lt;input type="number" name="attr_BARPHY_CLASSE_TOT" style="width:60px;" value="(2*@{BARPHY_CLASSE_POINTS} + @{BARPHY_CARAC1} + @{BARPHY_CARAC2} + @{BARPHY_CARAC3})" title="@{BARPHY_CLASSE_TOT} Total base classe" disabled /&gt; &lt;/td&gt; My objective would be too have some variables displayed in a span like this : &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!-- &lt;span name="attr_DEFPHY_CLASS_TOT_V1" value="@{DEFPHY_CLASSE_TOT}" readonly&gt;&lt;/span&gt; --&gt; I underestood how to do very basic sheetworkers, but I have not seen yet how to handle the select fields that depends on autocalc automatically, aside from writting a manual parser each time.
Alright so I have started using switch statements to redo everything in sheet workers. However this seems tedious, so if anyone has any idea on how to do it in a better way, I am listening.
1579924068
GiGs
Pro
Sheet Author
API Scripter
You said there were versions of the code above for each stat. Can you list, without the html, the different stats. For instance, you have FORCE_CODE: FORCE_LETTERS + FORCE_DIVERS FORCE_TOT: round(@{FORCE_BASE}+(@{NIVEAU}*@{FORCE_CODE})/100) FOR: floor(@{FORCE}/2 - 5) BARPHY_CLASSE_TOT:&nbsp;(2*@{BARPHY_CLASSE_POINTS} + @{BARPHY_CARAC1} + @{BARPHY_CARAC2} + @{BARPHY_CARAC3}) In the meantime, for those selects change as follows &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;input type="hidden" name="attr_BARPHY_CARAC2" &gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;select class="sheet-carac-cust" name="attr_BARPHY_CARAC1_SELECT" style="width:37px;" title="@{BARPHY_CARAC1}" size="1"&gt; &lt;option value="0"&gt;-&lt;/option&gt; &lt;option value="FOR"&gt;FOR&lt;/option&gt; &lt;option value="DEX"&gt;DEX&lt;/option&gt; &lt;option value="CON"&gt;CON&lt;/option&gt; &lt;option value="INT"&gt;INT&lt;/option&gt; &lt;option value="SAG"&gt;SAG&lt;/option&gt; &lt;option value="CHA"&gt;CHA&lt;/option&gt; &lt;option value="NIVEAU/2" selected&gt;NIV+&lt;/option&gt; &lt;option value="NIVEAU/4" &gt;NIV&lt;/option&gt; &lt;/select&gt;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;input type="hidden" name="attr_BARPHY_CARAC2" &gt; &lt;select class="sheet-carac-cust" name="attr_BARPHY_CARAC2_SELECT" style="width:37px;" title="@{BARPHY_CARAC2}" size="1"&gt; &lt;option value="0" selected&gt;-&lt;/option&gt; &lt;option value="FOR"&gt;FOR&lt;/option&gt; &lt;option value="DEX"&gt;DEX&lt;/option&gt; &lt;option value="CON"&gt;CON&lt;/option&gt; &lt;option value="INT"&gt;INT&lt;/option&gt; &lt;option value="SAG"&gt;SAG&lt;/option&gt; &lt;option value="CHA"&gt;CHA&lt;/option&gt; &lt;/select&gt; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &lt;input type="hidden" name="attr_BARPHY_CARAC3" &gt; &lt;select class="sheet-carac-cust" name="attr_BARPHY_CARAC3_SELECT" style="width:37px;" title="@{BARPHY_CARAC3}" size="1"&gt; &lt;option value="0" selected&gt;-&lt;/option&gt; &lt;option value="FOR"&gt;FOR&lt;/option&gt; &lt;option value="DEX"&gt;DEX&lt;/option&gt; &lt;option value="CON"&gt;CON&lt;/option&gt; &lt;option value="INT"&gt;INT&lt;/option&gt; &lt;option value="SAG"&gt;SAG&lt;/option&gt; &lt;option value="CHA"&gt;CHA&lt;/option&gt; &lt;/select&gt; Notice I've changed the attribute names of the selects, and added&nbsp; a hidden input with the old name. This lets macros, sheet workers, and autocalc fields&nbsp; work the same as before, once the following sheet worker is set up: [1,2,3].forEach(num =&gt; { &nbsp; &nbsp; on(`change:barphy_carac${num}_select `, () =&gt; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; getAttrs([` BARPHY_CARAC${num}_SELECT`,'FOR','DEX','CON','INT','SAG','CHA','NIVEAU'], (values) =&gt; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; const which = values[ ` BARPHY_CARAC${num}_SELECT`]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; const score = which === "0" ? 0 : ((which.length &gt; 3) ? (+values['NIVEAU'] || 0) / (+which.split("/")[1] || 2) : 2 * (+values[which] || 0)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; setAttrs({&nbsp;[ ` BARPHY_CARAC${num}`]: score }); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; }); &nbsp; &nbsp; }); }); This handles all three selects, and saves the appropriate scores into the hidden attribute. The score calculation is pretty complex. But basically it first looks to see if the select value is 0, and if so sets score to that value. If not, it checks if it is longer than 3 letters - which means it must be one of the two NIVEAU options, and gets NIVEAU and divides it by the appropriate number. And if its not NIVEAU, the only options left are the three letter stat codes, and it grabs the value of the appropriate one and doubles it. As long as the attributes this function depends on (those listed in the getAttrs) are created with sheet workers and not autocalcs, this will calculate the values properly. If you supply the missing attributes, I can create a set of workers that will calculate the other attributes.
on([ "sheet:opened", "change:force_base", "change:force_lettre", "change:force_divers", "change:niveau" ].join(" "), function () { getAttrs(["FORCE_BASE", "FORCE_LETTRE", "FORCE_DIVERS", "DEXTERITE_BASE", "DEXTERITE_LETTRE", "DEXTERITE_DIVERS", "CONSTITUTION_BASE", "CONSTITUTION_LETTRE", "CONSTITUTION_DIVERS", "INTELLIGENCE_BASE", "INTELLIGENCE_LETTRE", "INTELLIGENCE_DIVERS", "SAGESSE_BASE", "SAGESSE_LETTRE", "SAGESSE_DIVERS", "CHARISME_BASE", "CHARISME_LETTRE", "CHARISME_DIVERS", "NIVEAU"], function(v) { var force = Math.round(parseInt(v["FORCE_BASE"],10) + (parseInt(v["FORCE_LETTRE"],10)+ parseInt(v["FORCE_DIVERS"],10)) * parseInt(v["NIVEAU"],10) / 100); var dexterite = Math.round(parseInt(v["DEXTERITE_BASE"],10) + (parseInt(v["DEXTERITE_LETTRE"],10)+ parseInt(v["DEXTERITE_DIVERS"],10)) * parseInt(v["NIVEAU"],10) / 100); var constitution = Math.round(parseInt(v["CONSTITUTION_BASE"],10) + (parseInt(v["CONSTITUTION_LETTRE"],10)+ parseInt(v["CONSTITUTION_DIVERS"],10)) * parseInt(v["NIVEAU"],10) / 100); var intelligence = Math.round(parseInt(v["INTELLIGENCE_BASE"],10) + (parseInt(v["INTELLIGENCE_LETTRE"],10)+ parseInt(v["INTELLIGENCE_DIVERS"],10)) * parseInt(v["NIVEAU"],10) / 100); var sagesse = Math.round(parseInt(v["SAGESSE_BASE"],10) + (parseInt(v["SAGESSE_LETTRE"],10)+ parseInt(v["SAGESSE_DIVERS"],10)) * parseInt(v["NIVEAU"],10) / 100); var charisme = Math.round(parseInt(v["CHARISME_BASE"],10) + (parseInt(v["CHARISME_LETTRE"],10)+ parseInt(v["CHARISME_DIVERS"],10)) * parseInt(v["NIVEAU"],10) / 100); setAttrs({ ["FORCE"] : force, ["FOR"] : Math.floor(force/2 - 5), ["DEXTERITE"] : dexterite, ["DEX"] : Math.floor(dexterite/2 - 5), ["CONSTITUTION"] : constitution, ["CON"] : Math.floor(constitution/2 - 5), ["INTELLIGENCE"] : intelligence, ["INT"] : Math.floor(intelligence/2 - 5), ["SAGESSE"] : sagesse, ["SAG"] : Math.floor(sagesse/2 - 5), ["CHARISME"] : charisme, ["CHA"] : Math.floor(charisme/2 - 5) }) }); }); on([ "sheet:opened", "change:atkcac_carac1_selector" ].join(" "), function () { getAttrs(["FORCE_BASE", "FOR", "DEXTERITE_BASE", "DEX", "CONSTITUTION_BASE", "CON", "INTELLIGENCE_BASE", "INT", "SAGESSE_BASE", "SAG", "CHARISME_BASE", "CHA", "atkcac_carac1_selector"], function(v) { var caracval = 0; switch(parseInt(v["atkcac_carac1_selector"],10)) { case 0: caracval=0 break; case 1: caracval=parseInt(v["FORCE_BASE"],10) + parseInt(v["FOR"],10); break; case 2: caracval=parseInt(v["DEXTERITE_BASE"],10) + parseInt(v["DEX"],10); break; case 3: caracval=parseInt(v["CONSTITUTION_BASE"],10) + parseInt(v["CON"],10); break; case 4: caracval=parseInt(v["INTELLIGENCE_BASE"],10) + parseInt(v["INT"],10); break; case 5: caracval=parseInt(v["SAGESSE_BASE"],10) + parseInt(v["SAG"],10); break; case 6: caracval=parseInt(v["CHARISME_BASE"],10) + parseInt(v["CHA"],10); break; } setAttrs({ ["ATKCAC_CARAC1"] : caracval }) }); }); ^ Above is my current code that I did before reading your answer. It seems like I did something similar to you, but I instead used a hardcoded switch-case rather than parsing logic. As for other attributes I have DEXTERITE/DEX, CONSTITUTION/CON, INTELLIGENCE/INT, SAGESSE/SAG, CHARISME/CHA which are equivalent to FORCE/FOR. (With the usual CODE, LETTRE, TOT for everyone. Note : I have now renamed FORCE_TOT to simply FORCE, before I needed two names because I had two different autocalc names displaying the same number.) Aside from BARPHY[...], I also have BARMAG, DEFPHY,DEFMAG, that have similar fields (_CARAC1/2/3, ...). But I think you've given me enough info to figure out everything else by myself for now. (But if I made any major mistake in my code, please do tell me).
1579988948
GiGs
Pro
Sheet Author
API Scripter
Your code is good! A couple of tips for using parseint&nbsp; parseInt(v["CONSTITUTION_BASE"],10) You dont need the ,10 part, so you can do this as parseInt(v["CONSTITUTION_BASE"]) Also, if someone enters a text value in one of the attributes (or even a space), the worker will crash and not return a value. You can handle invalid inputs like this parseInt(v["CONSTITUTION_BASE"]) || 0 The || 0 &nbsp;part says OR 0 , and means if the first part returns an invalid value, it will use 0 instead of an error. It's a great way to set a default value. You have to be careful when using this though when doing calculations like this var force = Math.round(parseInt(v["FORCE_BASE"]) + (parseInt(v["FORCE_LETTRE"])+ parseInt(v["FORCE_DIVERS"]) * parseInt(v["NIVEAU"],10) / 100); You need to put brackets around each entry, including the || 0&nbsp; part like this var force = Math.round((parseInt(v["FORCE_BASE"])||0) + ((parseInt(v["FORCE_LETTRE"])||0)+ (parseInt(v["FORCE_DIVERS"])||0) * (parseInt(v["NIVEAU"],10)||0) / 100); Looking at that I wonder if the brackets are in the correct place to begin with, lets break down the original form var force = &nbsp;&nbsp;&nbsp;&nbsp;Math.round(&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parseInt(v["FORCE_BASE"]) + (parseInt(v["FORCE_LETTRE"])+ parseInt(v["FORCE_DIVERS"]) * parseInt(v["NIVEAU"],10) / 100 &nbsp;&nbsp;&nbsp;&nbsp;); Should there be a set of brackets around base + lettre + divers, before multiplying by niveau? Anyway back to parseInt. One technique to simplify its use is to create a parsing function at the start of your script block, like function int(score, default) { &nbsp; &nbsp; return parseInt(score) || default; } you can also write this function like this const int = (score,default) =&gt; parseInt(score) || default; The second version is more compact, the first is easier to understand for a lot of people. There is no difference between them though - use whichever you like. Then you can write your code like this var force = Math.round(int(v["FORCE_BASE"]) + (int(v["FORCE_LETTRE"])+ int(v["FORCE_DIVERS"]) * int(v["NIVEAU"]) / 100); It's easier to read, and easier to spot problems with complex calculations. Something else you can do to streamline it is use the dot syntax for getting values from getAttrs. Instead of doing this parseInt(v["CONSTITUTION_BASE"]) you can do this parseInt(v.CONSTITUTION_BASE) or, using the int function int(v.CONSTITUTION_BASE) If the attribute contains no special characters in its name, this is simplay way to write them, and its easier to read. There are two problems with your first macro, which I'll cover in another post since this long already.
1579990762

Edited 1579990848
GiGs
Pro
Sheet Author
API Scripter
Two problems with your first macro: in the on(change part, you only declare the force attributes, not dexterity, sagasse, etc, so the worker wont update when those stats change. The second issue is that you lump all the attributes in one sheet worker. When fort changes, your dexterity, sagasse and all the other attributes are reclaculated, even though those stats havent changed. Before I go on, let me stress seriously , this isnt a serious problem in this instance. I just like the elegance of the approach I'm about to describe. But you can ignore what I'm about to suggest and keep using the worker you already have - just remember to update the change event so all appropriate stats are being monitored for changes. So on to the extra method, which i call Universal Sheet Workers (as I linked earlier in the thread). This is a method of managing multiple sheet workers that basically do identical things, but just change the attribute names. You have the core attributes of&nbsp; 'FORCE','DEXTERITE','CONSTITUTION','INTELLIGENCE','SAGESSE','CHARISME' and need to perform identical calculations for each, which makes it perfect for this approach. This following function will automagically create a sheet worker for each of those core stats, and perform the calculations. ['FORCE','DEXTERITE','CONSTITUTION','INTELLIGENCE','SAGESSE','CHARISME'].forEach(stat&nbsp;=&gt;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;\\ this will loop through each core attribute, and "stat" will be substituted for one of the attribute names on each loop. &nbsp;&nbsp;&nbsp;&nbsp;on([ &nbsp;&nbsp;&nbsp;&nbsp;"sheet:opened", &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_base`, \\ we are using template literal syntax here: that allows strings to be more dynamic. &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_lettre`, \\ the ${ } part lets you put code isnide of it, which will be calculated as needed &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_divers`, &nbsp;&nbsp;&nbsp;&nbsp;"change:niveau" &nbsp;&nbsp;&nbsp;&nbsp;].join("&nbsp;"),&nbsp;function&nbsp;()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getAttrs([`${stat}_BASE`,&nbsp;`${stat}_LETTRE`,&nbsp;`${stat}_DIVERS`,&nbsp;"NIVEAU"],&nbsp;function(v)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;score&nbsp;=&nbsp;Math.round( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( int(v[`${stat}_BASE`])&nbsp;+&nbsp;int(v[`${stat}_LETTRE`])&nbsp;+&nbsp;int(v[`${stat}_DIVERS`]) )&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;int(v.NIVEAU)&nbsp;/&nbsp;100 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;); &nbsp;&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;&nbsp;[stat]&nbsp;:&nbsp;score, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[stat.substring(0,3)]&nbsp;:&nbsp;Math.floor(score/2&nbsp;-&nbsp;5) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}); }); Note: This assumes you've added that int function from the previous post. Sio with one function we have created 6 sheet workers, one for each of the core attributes. You should double check the score the function, that the brackets are in the right place. I've added line breaks to make it clearer whats happening in that calculation. One final point. If you were absolutely concerned with efficiency you'd change this part on([ &nbsp;&nbsp;&nbsp;&nbsp;"sheet:opened", &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_base`, &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_lettre`, &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_divers`, &nbsp;&nbsp;&nbsp;&nbsp;"change:niveau" &nbsp;&nbsp;&nbsp;&nbsp;].join("&nbsp;"),&nbsp;function&nbsp;()&nbsp;{ to on([ &nbsp;&nbsp;&nbsp;&nbsp;"sheet:opened", &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_base`, &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_lettre`, &nbsp;&nbsp;&nbsp;&nbsp;`change:${stat.toLowerCase()}_divers` &nbsp;&nbsp;&nbsp;&nbsp;].join("&nbsp;"),&nbsp;function&nbsp;()&nbsp;{ and then keep your original sheet worker as well as the one I posted here , and in that one, change this part on([ "sheet:opened", "change:force_base", "change:force_lettre", "change:force_divers", "change:niveau" ].join(" "), function () { to on([ "sheet:opened", "change:niveau" ].join(" "), function () { In other words, the new worker i posted has its change event responding only to each of the force, sagasse, etc, and their related attributes, but doesnt respond to niveau. While the original sheet worker only &nbsp;responds to niveau. The reason for this is niveau affects all the calculations, so a master sheet worker that updates all values based on it at once is efficient. This does lead to some duplication of code, so I generally wouldnt bother-&nbsp; just use the form I posted first. It's not going to make much difference in the long run. For that matter, you can use your own macro in place of the one i suggest here, and again, its not going to make that much difference.&nbsp; Anyway, whether you use this code or not, I present it to illustrate a technique that is very useful if you have to create a bunch of similar sheet workers.