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

New to sheet workers, not to programming, multiple on changes together

1623587730
Matthew C
Pro
Sheet Author
Hey all, as the title says, not new to programming, but I already hate working with sheet workers and trying to learn the syntax. Below is what I have: <div class="sheet-advanced__cell"> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Acuity</div> <input type="number" min="1" max="10" name="attr_acuity" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Vitality</div> <input type="number" name="attr_vitality" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Nerve</div> <input type="number" name="attr_nerve" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Essence</div> <input type="number" name="attr_essence" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> </div> <div class="sheet-advanced__cell"> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Durability</div> <input type="number" name="attr_durability" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Agility</div> <input type="number" name="attr_agility" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Comprehension</div> <input type="number" name="attr_comprehension" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> <div name="shell" class="sheet-bordered sheet-advancedJustify"> <div class="sheet-centertitle sheet-stamina1">Discernment</div> <input type="number" name="attr_discernment" value="1" class="sheet-currentStamina sheet-stamina2 sheet-centered"> </div> </div> As you can see here, they are simply a bunch of statistics that I want to limit between a 1 and a 10, but using the html min max does not do this, so I looked into sheetworkers and basically have this: <script type="text/worker"> on('change:vitality', () => { getAttrs(['vitality'], v=> { let vital = parseInt(v.vitality) || 0; let score = (vital > 10) ? 10 : (vital < 1 ? 1 : vital); setAttrs({ vitality: score }, {silent:true}); }); }); </script> But this would need to be made each time for each stat and what I would want is this (pseudo code) <script type="text/worker"> on('change:vitality change:durability change(the rest)', () => { getAttrs(['THE ATTRIBUTE CHANGED THAT CALLED THE SHEET WORKER'], v=> { let stat = parseInt(v.STAT THAT WAS CHANGED) || 0; let score = (stat > 10) ? 10 : (stat < 1 ? 1 : stat); setAttrs({ STAT THAT CHANGED: score }, {silent:true}); }); }); </script> and from what I understand sheet workers do record exactly which attribute called a sheet worker so I do believe this should be possible, but I am not sure where to start, any help to this or a huge document with advanced sheetworker thingies would be awesome. the roll20 page for sheetworkers explains things but does not have enough examples of unique situations
1623591386

Edited 1623592616
GiGs
Pro
Sheet Author
API Scripter
This is ideal for Universal Sheet Workers . It might look like this: const stats = ['acuity', 'vitality', 'nerve', 'essence', 'the rest']; const watchstats = stats.map(stat => `change:${stat}`).join(' '); on(watchstats, (event) => {      if (event.info === 'sheetworker') return;        const stat = event.sourceAttribute;     const score = +event.newValue || 0;     const modified_score = Math.min(10, Math.max(1, score))     if(modified_score !== score) {         setAttrs({             [stat]: modified_score         });     }     });
1623592316

Edited 1623592708
GiGs
Pro
Sheet Author
API Scripter
To break it down, first create a variable to hold all the stat names - make sure you replace 'the rest' with the rest of your attributes: const stats = ['acuity', 'vitality', 'nerve', 'essence', 'the rest']; Then for the sheet worker you need to create an event line like on('change:acuity change:vitality ...', but you can contract that programmatically - that's what these two lines do: const watchstats = stats.map(stat => `change:${stat}`).join(' '); on(watchstats, (event) => {  the first line uses two functions. map: stats.map(stat => `change:${stat}`) This tasks an array (stats) and transform it into a new array, by taking each item in the array and doing something with it. In this case, it creates a string with "change:" at the start of each item. So you now have an array that looks like ['change:acuity', 'change:vitality', 'change:nerve', 'change:essence', 'the rest']; Then the second part join: .join(' ') takes an array, and joins each element into a string, and puts a space between each item. So you end up with const watchstats = 'change:acuity change:vitality change:nerve change:essence'; No you can just use on(watchstats to get that string, and set up a worker to watch all those stats. Now notice the end of that line: on(watchstats, (event) => {  event is an optional parameter, you don't need to supply it, but you can and when you do, it gives you a variable that contains information about what just changed - like the stat name and its new value. You can grab those values like this:     const stat = event.sourceAttribute;     const score = +event.newValue || 0;     The second line grans the stat value that just changed, and the + part makes sure its a number, while the || 0 sets it to 0 if its not a number. This is handy if your input is changed to an empty string, which could cause this worker to break. Then we convert it to a number between 1 and 10: const modified_score = Math.min(10, Math.max(1, score)) Math.min() sets the value to the minimum of the values, while Math.max() sets it to the highest value of the set. This expression makes sure the value is between 1 and 10. Now we check score against modified_score, and test if they are not equal  if(modified_score !== score) { If they are equal, the value must be between 1 and 10. But if they are not, we need to update the value on the sheet - that's what setAttrs does. Note that the setAttrs uses the stat name like [stat]:  setAttrs({             [stat]: modified_score         }); It needs to be inside brackets like that because it is a variable. The brackets force roll20 to resolve the stat into the name of the stat it represents. Finally, when you change an attribute, it will trigger the sheet worker again - since it has just changed. But we don't need to run it again. This line makes sure it doesn't:     if (event.info === 'sheetworker') return;  event also included whether the worker is running because of a players input, or because its being changed by a sheet worker. return makes sure the worker ends at this point if the value is set by a sheet worker. Note that you have {silent:true} in your sheet worker, which will do the same - but will also stop all other workers running from this change. It's possible you will later have sheet workers that calculate things based on the value of your attributes - the silent:true method would stop them updating. This method lets them keep working.
1623592904

Edited 1623592972
Matthew C
Pro
Sheet Author
Thanks for the reply, I was attempting this: on("change:vitality change:durability", function(eventInfo) { var stat = eventInfo.sourceAttribute; var value = eventInfo.newValue < 1 ? 1 : eventInfo.newValue > 10 ? 10 : eventInfo.newValue; setAttrs({ stat: value }); }); But it was not working, if I used a console log, the value of stat and the value of value was correct, but the attribute was not actually being changed on the character sheet EDIT: Doh! I forgot the brackets around the stat in setattrs
1623593332
GiGs
Pro
Sheet Author
API Scripter
I forgot them too and only noticed while writing the explanation post, and had to edit my post to include them :)
1623593483

Edited 1623593816
GiGs
Pro
Sheet Author
API Scripter
Your method doesnt test if eventInfo.newValue is a number. Check if the worker still works if you delete the value (changing it to an empty string).
1623593774

Edited 1623593808
Matthew C
Pro
Sheet Author
GiGs said: I forgot them too and only noticed while writing the explanation post, and had to edit my post to include them :) Thanks a bunch, found someone that did something similar to this with like 30 lines of code so I am glad I was able to simplify it, ended up with this: on("change:vitality change:durability change:agility change:comprehension change:discernment change:essence change:nerve change:acuity", function(eventInfo) { if (eventInfo.info === 'sheetworker') return; var stat = eventInfo.sourceAttribute; var v = eventInfo.newValue || 0; var value = v < 1 ? 1 : v > 10 ? 10 : v; setAttrs({ [stat]: value }); }); have not added the constant list etc as it is a total of 8 things, but may decide to if things change (number of items etc) GiGs  said: Your method doesnt test if eventInfo.newValue is a number. Check the worker still works if you delete the value (changing it to an empty string). Indeed, just noticed this so quickly added it in
1623593990
GiGs
Pro
Sheet Author
API Scripter
Looks good. And good point, you dont really need the stats constant - it just leads to a handy way to build up the change line without writing them all out :)
1623594082
Matthew C
Pro
Sheet Author
GiGs said: Looks good. And good point, you dont really need the stats constant - it just leads to a handy way to build up the change line without writing them all out :) Indeed, glad I am starting to get a feel for these workers, I usually work with C# and tbh the difference is not that bad :D