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

Problem: Making an attribute with a treshold adding "greater then" as value

Hello! I'm trying to create an attribute as a formula that sums/subtracts the difference between an attribute and a given number. Consider that i'm no expert of HTML and CSS, as I've just started experimenting with it. Anyway, I've find this formula: [[{0@{ATTRIBUTE}>7}*(0@{ATTRIBUTE}-6) + 0]] It works as a roll, but if I want to show the results directly on an attribute inside the sheet, it just displays the formula itself and not the result, even if I write it without the square brackets. Here's a further explanation on what I'm trying to do. I want to use the formula on two sheets. 1) Since I'm displeased with the official Pendragon 6 sheet, i'm working on a modified version of the Thierry MATTRAY Pendragon 5th ed. Sheet. In Pendragon 6 the passions are sorted in "Courts" with a total cap of 40 for each court, BUT - by rules - if your passion's value is bigger then 20, the court's cap count it ONLY as 20 anyway. As you can see below, I've already come out with a line for counting the total of a Court, but i'm struggle with the 20 cap for each of it's passions. Here's an example with the court FIDELITAS. That's where I want to display it (near the name): And That's my formula (I've only try an attemp with the Homage passion, named 'HOMA'. I named the formula for the treshold as 'HOMAcourt'. <label style='display:inline-block; width:180px;'>FIDELITAS</label> <input style='display:inline; width:30px; font-color:red;' type="text" name="attr_currentpas" value="(@{HOMA}+@{HOMAcourt}+@{LOY}+@{LOY2}+@{FEAL}+@{DUTY})" disabled="true"/> <label style='display:inline'>/</label> <input style='display:inline;' type="text" name="attr_attr_fidelitas" class="sheet-carac2" value="40" disabled="true" /> <br /> <label style='display:inline-block; color:black; margin-left: 5px; font-size:15px; color:black; width:60px;'>Homage</label> <input style='display:inline; margin-left: 10px; width:116px;' type="text" name="attr_HOMAWHO" class="sheet-long" /> <input style='display:inline;' type="number" name="attr_HOMA" class="sheet-carac2" value="0" /> <input style='display:inline;' type="number" name="attr_HOMAcourt" class="sheet-carac2" value="({0@{HOMA},0}>21*(0@{HOMA}-20) + 0)" /> <input style='display:inline; margin-left: 10px; margin-right: 10px;' type='checkbox' name='attr_CB-HOMA' value='1' /> <button type='roll' name='roll_TestHOMA' value="/em @{MYNAME} rolls HOMAGE @{HOMAWHO} with a modifier of ?{modif|0} : \n/roll 1d20+[[{{@{HOMA}+?{modif|0},0}>21}*(@{HOMA}+?{modif|0}-20) + 0]]<[[@{HOMA}+?{modif|0}]] \ HOMAGE @{HOMA} roll @{HOMA} modif. ?{modif|0}"></button> <br / 2) I'm working also on a modified version of Tony R. Cyberpunk 2020 Character Sheet for my own verision of the Interlock System. In this system, if you have 7 points in some of the Special Abilities, they will give you a bonus to certain other abilities/action/other stuff. For example, IF you have 7 points on the ability 'RICARICA VELOCE', (quick reload) you will get a +1 bonus to 'ARMI DA FUOCO' (firearms). That's where I want to display it (il'also circle the Special Ability in question that gives the bonus): That's the code with the formula: <tr> <td><input type='checkbox' class="sheet-classed" name='attr_RicaricaveloceChip' value='1'><span></span></td> <td><button type='roll' style="background-color: #272526; width:160px;" class="sheet-skillroll sheet-set" value='&{template:skill-general}{{skill= Ricarica Veloce}}{{character= @{character_name}, @{handle} }}{{total= [[1d10!!+@{Ricaricaveloce_Skill}+@{Ricaricaveloce_mod}+[[@{Ref}]]+?{Modifier|0}]] }} {{fumble= [***FUMBLE!***](!#fumblearma) }} '><label style=" color: white; text-align:center;" >Ricarica Veloce</label></button></td> <td><input type="text" style="width: 45px;" name="attr_Ricaricaveloce_Skill" Value="0" /></td> <td><input type="text" style="width: 45px;" name="attr_Ricaricaveloce_mod" Value='0'/></td> <input type="hidden" name="attr_RicaricaveloceBonus" disabled="disabled" value="[[{0@{Ricaricaveloce_Skill},0}>7*(0@{Ricaricaveloce_Skill}-6) + 0]]" /> </tr> ...A LOT OF LINES LATER... <table style="width:350px;"> <tr> <td><input type='checkbox' class="sheet-chipped" name='attr_ArmidafuocoChip' value='1'><span></span></td> <td><button type='roll' style="background-color: #272526; width:160px;"" class="sheet-skillroll sheet-set" value='&{template:skill-general}{{skill= Armi da fuoco}}{{character= @{character_name}, @{handle} }}{{total= [[1d10!!+@{Armidafuoco_Skill}+@{Armidafuoco_mod}+[[@{Ref}]]+?{Modifier|0}]] }} {{fumble= [***FUMBLE!***](!#fumblearma) }} '><label style="color: white; text-align:center;">Armi da fuoco</label></button></td> <td><input type="text" style="width: 45px;" name="attr_Armidafuoco_Skill" Value="0" /></td> <td><input type="text" style="width: 45px;" name="attr_Armidafuoco_mod" Value=' @{RicaricaveloceBonus} 'disabled="true"/></td> </tr> </table> Any suggestion? Thank you.
1720017426
Andreas J.
Forum Champion
Sheet Author
Translator
Yeah, using autocalc isn't great, there are issues: <a href="https://wiki.roll20.net/Autocalc#Problems_with_using_Auto-Calc" rel="nofollow">https://wiki.roll20.net/Autocalc#Problems_with_using_Auto-Calc</a> You'd want to write sheetworker functions that performs the calculations, so the attributes only have the intended value. Some really basic sheetworker examples can be seen on this page . This Glorantha example showcases a bit more complex stat calculation than the first example. Here is a really simple sheet using sheetworkers to calculate some of it's stats, if seeing a complete sheet example makes things easier to figure out. Of the top of m head I don't have any great &amp; simple example that resembles your calculation, but essentially if you can figure out how to write the calcualtion in javascript, it's just a bit more to make that work inside the sheetworker codeblock.
1720027572

Edited 1720203453
GiGs
Pro
Sheet Author
API Scripter
And if you want to learn sheet design from the basics to some pretty advanced stuff., check this out: <a href="https://cybersphere.me/roll20-sheet-author-master-list/" rel="nofollow">https://cybersphere.me/roll20-sheet-author-master-list/</a> I agree with Andreas that sheet workers are the way to go here but, unfortunately, there's not enough information on how to create them. First thing to note: sheet workers are completely incompatible with autocalcs, so any attribute that is calculated by sheet worker needs to have the disabled=true part removed. sheet workers wont touch those attributes. You can swap disabled=true with readonly . PS: I see my hand in all three of those examples, nice.
1720028213

Edited 1720028529
GiGs
Pro
Sheet Author
API Scripter
Actually, the interlock one (if Im understanding it) is pretty straightforward. You just need to swap this: &lt;input type="hidden" name="attr_RicaricaveloceBonus" disabled="disabled" value="[[{0@{Ricaricaveloce_Skill},0}&gt;7*(0@{Ricaricaveloce_Skill}-6) + 0]]" /&gt; with new HTML for that and a sheet worker. the new HTML would be &lt;input type="hidden" name="attr_RicaricaveloceBonus" value="0" &gt; And the sheet worker (which must go intheir own script block): (if it's just to get a +1 bonus, which doesn't look like your input) on ( 'change:ricaricaveloce_skill' , () =&gt; { &nbsp; &nbsp; getAttrs ([ 'Ricaricaveloce_Skill' ], values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const Ricaricaveloce_Skill = parseInt ( values . Ricaricaveloce_Skill ) || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; const RicaricaveloceBonus = &nbsp; Ricaricaveloce_Skill &gt; = 7 ? 1 : 0 ; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RicaricaveloceBonus : RicaricaveloceBonus &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; }); }); If instead, you get a bonus of +1 per value above 6, you could do it like this (just one line is different): on ( 'change:ricaricaveloce_skill' , () =&gt; { &nbsp; &nbsp; getAttrs ([ 'Ricaricaveloce_Skill' ], values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const Ricaricaveloce_Skill = parseInt ( values . Ricaricaveloce_Skill ) || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; const RicaricaveloceBonus = &nbsp; Math . max ( 0 , Ricaricaveloce_Skill - 6 ); &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RicaricaveloceBonus : RicaricaveloceBonus &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); }); Creating sheet workers is way more pain that autocalcs, but they are superior in every way. To use this, you'd just add +@{RicaricaveloceBonus} wherever you'd need it. If you want to create sevaral similar bonuses, there are shortcuts so that you don't have to type the whole thing out multiple times, but we'd need to know what all those bonuses are, the names of the stats that they are calculated form, and the name that contains the final bonus.
1720028347
GiGs
Pro
Sheet Author
API Scripter
Also, you use Ricaricaveloce_Skill and RicaricaveloceBonus - i'd go for conistency and use Ricaricaveloce_Skill and Ricaricaveloce_Bonus
1720028817

Edited 1720202101
GiGs
Pro
Sheet Author
API Scripter
The pendragon one is trickier to describe, but you can probably get rid of a lot of html, and just have one sheet worker per Court (or maybe one for all the courts), and calculate whether each Court is under 40, and show that somewhere.
1720045682

Edited 1720046742
Ok, i think this may be a little bit to much for my actual skill level ahaha Thank you Andreas, and thank you GiGs! GiGs, Even if I actually cannot understand how to read your formula, since all of the modifier act the same, (7 points it's the actual skill cap, so your first solution is the right one) I have no problem calculating the other Special Abilities bonuses just changing the name of the attributes in your formula with the ones that I needed. Also, i've figure out a way to make it appear where i wanted to: i just modified the name of every "mod" attribute&nbsp; where the bonuses had to appear with "Ricaricaveloce_Bonus" (Yeah, i've also taken your 'consistency' suggestion) and it worked. BUT for the passion's courts in Pendragon, I have no clue where to start. Can you help me with that? Let's say that I want to calculate the capped passions of the Fidelitas (attribute name: 'currentfidel ') court: the passions are Homage ('HOMA'), Loyalty 1 (' LOY '), Loyalty 2 (' LOY2 '),&nbsp; Fealty (' FEAL '), Duty (' DUTY '). But i need those attribute to be 'normal' and without the 20 cap, since i need them for the rolls. So, having 5 passions in total, i need 5 others attribute related to them and WITH the cap of 20 (maybe same name but wit ' court ' attached, as, for example ' HOMAcourt ') to calculate the actual value of the court.
1720047747

Edited 1720048377
GiGs
Pro
Sheet Author
API Scripter
I was going to suggest something more concrete for passions, but noticed a possible contradition that threw me. See this part of the autocalc: value="(@{HOMA}+@{HOMAcourt}+@{LOY}+@{LOY2}+@{FEAL}+@{DUTY})" disabled="true"/&gt; You have HOMA and HOMAcourt both in there, but HOMAcourt is calculated from HOMA, so I wondered if that was right. For the Passions, yes, I can tell you how to do that, but I need to know why that is repeated there. What is the 20 cap used for? Having the raw passion (without the 20 cap) in one place would be done normally, then you could have a sheet worker that calculates the total value of all the stats taking the 20 cap into account. You wouldn't need a separate input for each stat - just one for the total of all the passions. What is that total used for?
1720048284

Edited 1720048535
GiGs
Pro
Sheet Author
API Scripter
PS, the way this works: const Ricaricaveloce_Bonus = &nbsp;Ricaricaveloce_Skill &gt;= 7 ? 1 : 0; This is something called a ternary operator, which is a fancy way to state it's a toggle. It's a way to choose between two values, in this case 1 and 0. The format is this VARIABLE = EXPRESSION ? RESULT IF EXPRESSION IS TRUE : RESULT IF EXPRESSION IS FALSE basically you look for the =, the ?, and the : symbols. They divide the expression into 4 sections: BEFORE THE EQUALS: a variable declaration - the name of the thing you result will be saved into BETWEEN THE EQUALS AND ?: this is where you put the expression, a calculation that results in true or false BETWEEN ? AND : The True result. When the expression produces a true value. AFTER : The false result, when the expression produces an untrue result. So looking back at this const Ricaricaveloce_Bonus = &nbsp;Ricaricaveloce_Skill &gt;= 7 ? 1 : 0; we have the VARIABLE const Ricaricaveloce_Bonus The EXPRESSION Ricaricaveloce_Skill &gt;= 7 The value if TRUE 1 The value if FALSE 0 I use ternary operators all the time-&nbsp; the number of times you need a test between 2 values is staggering.
Ok, explanation clear, thank you! As for the courts, actually that was an old formula. I was thinking that 'Homacourt' may have been the difference between raw passion and 20 (ex 26-20=6) and not the already capped passion. So no, i don't need the original attribute in the formula now.
1720121023

Edited 1720203795
GiGs
Pro
Sheet Author
API Scripter
There are a number of ways to do this. I'll start with an easy one (not actually the easiest, but understandable I think) and go to more advanced. First things to understand: Javascript and HTML are different environments, the script block is created inside HTML but does not use the same language. JS and HTML treat the case of attribute names differently. In HTML, case doesn't matter - you can create an attribute named HOMA, but can address it as @{HOMA} or @{homa}, or {hOmA} - case does not matter. In JS, case does matter - but there are special, um, cases. For this reason, it's always best to refer to attributes in lower case, however they are defined in the HTML. So don't use HOMA, use homa. If needed, convert them into lower case whenever possible. So here's a fairly simple way to do this (it might not look simple, trust me on that): on ( 'change:homa change:loy change:loy2 change:feal change:duty' , () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ([ 'homa' , 'loy' , 'loy2' , 'feal' , 'duty' ], values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let total = 0 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [ 'homa' , 'loy' , 'loy2' , 'feal' , 'duty' ]. forEach ( passion =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const temp = parseInt ( values [ passion ]) || 0 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; total += Math . min ( 20 , temp ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fidelitas_total: total &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); &nbsp; &nbsp; The first line is called the event line. You set up a "watcher" for the attributes listed - these are triggers for the sheet worker. Whenever any of those attributes change, the worker is triggered and does everything after that line. The second line is the collection line. Sheet workers do not know anything about the character sheet. You have to use a getAttrs function to collect information about the character. We give it a list of attribute names, and it grabs the values for those attributes and stores them in a variable, here called values but you can change that. What follows is the body of a sheet worker - where what the worker actually does happens. We start off by creating a variable with a value of 0, and are going to go through each passion and add its score to the total. But we want to limit that score to 20. The forEach function loops through an array - here we have an array of all the attribute names, and temporarily call whichever the current one is being acted on 'passion'. In this way, we don't need to know how many things are in the initial array or what they arecalled, it just goes through them all. This line const temp = parseInt(values[passion]) || 0; grabs the value of an attribute by its name. It looks complicated, but it is just grabbing an attribute fromn the values object we created earler, converting it into a number ( parseInt ), and if there's an error, setting its value to 0 ( ||0 ). Then we have this line: total += Math.min(20, temp); Math.min() is a function that takes the smaller value of whatever you give it. In this case, it'll use the attribute value, or 20, whichever is smaller, and then adds it to total . Finally you have the setAttrs line - this sets attribute values, storing them for later. So in the HTML, you'd need an input like this: &lt;input type="number" name="attr_fidelitas_total" value="0" readonly&gt; Any time any of those passions changed, a total would be recalculated and again saved there. I dont know what you want to do with it or how to use it, so just created a visible number input. You could change that. I have described one way to create the passion calculating sheet worker. There are numerous other ways to do the same thing. Here's a much more advanced version: const changes = arr =&gt; arr . reduce (( all , one ) =&gt; ` ${ all } change: ${ one } ` , '' ); &nbsp; &nbsp; const int = ( score , fallback = 0 ) =&gt; parseInt ( score ) || fallback ; &nbsp; &nbsp; const fidelitas = [ 'homa' , 'loy' , 'loy2' , 'feal' , 'duty' ]; &nbsp; &nbsp; on ( changes ( fidelitas ), () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ( fidelitas , values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const total = Object . values ( values ). reduce (( all , one ) =&gt; all + int ( values [ one ]), 0 ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fidelitas_total: total &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); This uses some more advanced functions, but does exactly the same thing. If you were creating a lot of similar sheet workers, this approach (tweaked a little) would lead to less work in the long-term, but if just creating one sheet worker, it just doesn't matter. You can see from these examples and the previous ones that many sheet workers follow a very similar template. The first two ines and the final setAttrs form the basic template, and the body part can be very varied. The more similar they are, the easier it is to come up with rules to automate the sheet workers and reduce the work a lot.
1720130976

Edited 1720132885
Gigs, thank you very much for your patience and willingness to explain. The explanation is crystal clear, and i've understand the entire procedure. Only problem is that i lack the very basic grammar of JS: I managed to mime your work with the other courts and they all works, but some basic elements remains obscure to me [like " () =&gt; { " ] Still, since we are here, I tried working on another sheet worker related to another rule of my version of the Interlock system: by the rules, every 3 pieces of iron armor you wear, you get a -1 penalty to some skills. I tried to modify the second formula you made for 'Ricaricaveloce ', but for some reason, it doesn't work. Here's the code on('change:pezzidiferro', () =&gt; { getAttrs(['pezzidiferro'], values =&gt; { const pezzidiferro = parseInt(values.pezzidiferro) || 0; const pezzidiferro_malus = Math.max(0, pezzidiferro -3); setAttrs({ pezzidiferro_malus: pezzidiferro_malus }); }); }); What am I doing wrong? Also, does this formula works as 'adding a bonus every 3 points', or as 'adding a bonus for EACH point above 3'?
1720141589

Edited 1720142862
GiGs
Pro
Sheet Author
API Scripter
You've done really well. When you say it doesn't work, do you mean it works but gives the wrong answer, or that nothing is happening? Since that is for every 3 points, you want to divide by 3, and probably round down (the Math.floor function handles that - the Math object allows access to a lot of mathematical functions). So, here's what I'd do: on('change:pezzidiferro', () =&gt; { getAttrs(['pezzidiferro'], values =&gt; { const pezzidiferro = parseInt(values.pezzidiferro) || 0; const pezzidiferro_malus = Math.floor(pezzidiferro / 3); setAttrs({ pezzidiferro_malus: pezzidiferro_malus }); }); });
1720142704

Edited 1720202428
GiGs
Pro
Sheet Author
API Scripter
You mentioned confusion about this syntax: on('change:pezzidiferro', () =&gt; { getAttrs(['pezzidiferro'], values =&gt; { This might get a bit arcane. I hope you can follow this, but this is really important: you don't have to. You can just copy and use it for now, and you'll figure it out later. In the old days of JavaScript, this was written like this: on('change:pezzidiferro', function() { getAttrs(['pezzidiferro'], function(values) { There's a thing called a function, which allows you to perform operations inside it. You can nest functions inside of other functions. That's what is going on here. The first line creates the event function, and the rest of the sheet worker goes inside it. Then the next line creates the getAttrs function, and makes it a bit more clear (if you know the syntax), that it is creating something called values and passing that to the body of the function. Recall that values contains all the attributes that getAttrs has named. Then an update to JavaScript came along and introduced something called Arrow Functions. That's what I've used in my sheet workers. I like the syntax of these better. There are subtle improvements with arrow functions, but for these it really makes no difference - you can use either form. I prefer the arrow form for consistency - I use arrow functions in other places. Lets look at that forEach loop from earlier: ['homa', 'loy', 'loy2', 'feal', 'duty'].forEach(passion =&gt; { const temp = parseInt(values[passion]) || 0; &nbsp; &nbsp;total += Math.min(20, temp); }); That can be written with the older function syntax like this: ['homa', 'loy', 'loy2', 'feal', 'duty'].forEach(function(passion) { const temp = parseInt(values[passion]) || 0; &nbsp; &nbsp;total += Math.min(20, temp); }); This makes it a little more clear that the forEach function is a function. For our use, both do exactly the same thing and again, which to use this comes down to a personal preference. The real advantage of them comes in things like the int function I sometimes use (and many others, but this is very common): const int = (score,fallback = 0) =&gt; parseInt(score) || fallback; To understand what this does, look at this line from your code: const pezzidiferro = parseInt(values.pezzidiferro) || 0; This extracts an attribute from the values object, and makes sure its a number (technically, an integer). You do this a lot, and whenever you have repeated code, its a good idea to change into a reusable function. You could put this at the start of your script block: function int(score, fallback = 0) { const value = parseInt(score) || 0; return value; }); You pass some values to the function, perform one or more calculations, then return a value to wherever the function was called from. You would then be able to use that function anywhere in your code. Instead of writing this: const pezzidiferro = parseInt(values.pezzidiferro) || 0; you could write const pezzidiferro = int(values.pezzidiferro); That's not that much shorter, but it is shorter, and it's something you do a lot so the savings add up. It usually turns out more than worth it. But this is also where arrow functions really shine. Instead of writing this: function int(score, fallback = 0) { const value = parseInt(score) || 0; return value; }); you can write: const int = (score,fallback = 0) =&gt; parseInt(score) || fallback; You move some of the syntax around: instead of function(something)&nbsp; { it becomes const something =&gt; {&nbsp; and you can skip the return statement. You can usually condense simple functions to a single line. So that's why that is written like that!
1720144191

Edited 1720144677
GiGs
Pro
Sheet Author
API Scripter
I'm now going to show you something that might blow your mind even more. Back in the other thread you posted a great explanation, and this set of workers: on('change:homa change:loy change:loy2 change:feal change:duty', () =&gt; { getAttrs(['homa', 'loy', 'loy2', 'feal', 'duty'], values =&gt; { let total = 0; ['homa', 'loy', 'loy2', 'feal', 'duty'].forEach(passion =&gt; { const temp = parseInt(values[passion]) || 0; total += Math.min(20, temp); }); setAttrs({ fidelitas_total: total }); }); }); on('change:homa change:hat change:hat2 change:hat3 change:hat4 change:lov change:lov2 change:lov3', () =&gt; { getAttrs(['hat', 'hat1', 'hat2', 'hat3', 'hat4', 'lov', 'lov2', 'lov3'], values =&gt; { let total = 0; ['hat', 'hat1', 'hat2', 'hat3', 'hat4', 'lov', 'lov2', 'lov3'].forEach(passion =&gt; { const temp = parseInt(values[passion]) || 0; total += Math.min(20, temp); }); setAttrs({ fervor_total: total }); }); }); on('change:devo change:ador', () =&gt; { getAttrs(['devo', 'ador'], values =&gt; { let total = 0; ['devo', 'ador'].forEach(passion =&gt; { const temp = parseInt(values[passion]) || 0; total += Math.min(20, temp); }); setAttrs({ adoration_total: total }); }); }); on('change:chiva change:hos change:stati', () =&gt; { getAttrs(['chiva', 'hos', 'stati'], values =&gt; { let total = 0; ['chiva', 'hos', 'stati'].forEach(passion =&gt; { const temp = parseInt(values[passion]) || 0; total += Math.min(20, temp); }); setAttrs({ civilitas_total: total }); }); }); I'm going to show you one way to condense this massively - you could condense that whole thing above to this: const changes = arr =&gt; arr . reduce (( all , one ) =&gt; ` ${ all } change: ${ one } ` , '' ); &nbsp; &nbsp; const int = ( score , fallback = 0 ) =&gt; parseInt ( score ) || fallback ; &nbsp; &nbsp; const passions = { &nbsp; &nbsp; &nbsp; &nbsp; fidelitas: [ 'home' , 'loy' , 'loy2' , 'feal' , 'duty' ], &nbsp; &nbsp; &nbsp; &nbsp; fervor: [ 'hat' , 'hat1' , 'hat2' , 'hat3' , 'hat4' , 'lov' , 'lov2' , 'lov3' ], &nbsp; &nbsp; &nbsp; &nbsp; adoration: [ 'devo' , 'ador' ], &nbsp; &nbsp; &nbsp; &nbsp; civilitas: [ 'chiva' , 'hos' , 'stati' ] &nbsp; &nbsp; }; &nbsp; &nbsp; Object . keys ( passions ). forEach ( this_passion =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on ( changes ( passions [ this_ passion ]), () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ( passions [ this_ passion ], values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const total = Object . values ( values ). reduce (( all , one ) =&gt; all + int ( values [ one ]), 0 ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [ ` ${ this_ passion } _total` ]: 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; &nbsp; &nbsp; }); &nbsp; &nbsp; }); That is extremely complex and I can try to explain how it works, but there's a lot going on there and chances are you've got the passions done and don't need this. But for the record, if the number of your passions changed, or the attributes or their names that they contained changed, you would only need to change this part: const passions = { &nbsp; &nbsp; &nbsp; &nbsp; fidelitas: [ 'home' , 'loy' , 'loy2' , 'feal' , 'duty' ], &nbsp; &nbsp; &nbsp; &nbsp; fervor: [ 'hat' , 'hat1' , 'hat2' , 'hat3' , 'hat4' , 'lov' , 'lov2' , 'lov3' ], &nbsp; &nbsp; &nbsp; &nbsp; adoration: [ 'devo' , 'ador' ], &nbsp; &nbsp; &nbsp; &nbsp; civilitas: [ 'chiva' , 'hos' , 'stati' ] &nbsp; &nbsp; }; Handy if you want to increase the number of hates and loves, for example. The rest of the code needs not be touched (unless I have any typos sneaking in there - always a danger with complex code!). But you can see there the name of the group, then an array of the passions in that group. The function loops through that group of passions, and does its stuff with one group at a time, without needing to duplicate the&nbsp; code for each group. When you have a bit more experience under your belt, you'll see more places you can duplicate code, and end up making structures like these. And the beauty of coding is, you can build up to this slowly - using ever so slightly more complex code as you go.
1720168207

Edited 1720168321
GiGs said: I'm now going to show you something that might blow your mind even more. Well, you are right. That has blow my mind ahah During this time that I worked with html, I started to think that programming languages ​​could only act in an extremely dumb and straightforwad way, but that is something else. I'm definitely gonna swap my code with yours. Seems "easier" to change in case i have to add other passions. Any, your explanation was very clear this time too. Thank you! As for the the "pezzidiferro" code, when I said it didn't work, I meant something else. On the character sheet on which I was testing it I already had an attribute called "pezzidiferro" with a value of 3 (The new sheet is meant to convert an already existing campaign). Not having changed it, the penalty did not appear. In this actually the Sheet Workers seems a little bit dumb: if i don't alter an already exisiting attribute, even if it already meets the prerequisite for the code to work, the code doesn't start. Anyway, i'm gonna use your code for that too, thanks!
1720198930

Edited 1720204058
GiGs
Pro
Sheet Author
API Scripter
LU MASTER said: Well, you are right. That has blow my mind ahah During this time that I worked with html, I started to think that programming languages ​​could only act in an extremely dumb and straightforwad way, but that is something else. I'm glad to hear it :) One thing to be aware of, diehard programmers will tell you that HTML and CSS are not actually programming langages (and they are right, if a little snobby). So, don't draw too many ideas about what programming is like from them. You can do amazing things with some programming languages. Anyway, back to your "pezzidiferro" situation. The code should change already existing attributes. One thing to be tested during the design stage (this only happens during design, and is probably strictly a custom sandbox issue): If something isn't working but you know it should be working, create a new character and try it on that. Character sheets can become corrupt after many, many changes (in the sandbox), and you might have to abandon that character and work with a new one. Don't get too sensitive to this issue - it's not something that happens often - but when things aren't behaving as they should, it's time to check it. (Also, recall the sheet workers cannot change disabled attributes - so if that original input had disabled in it, the sheet worker would not change it.)
1720212976

Edited 1720213435
I will check the "pezzidiferro" code next week, I'm little bit busy these days. But returning to your last code for the Courts, I've tried it, and for some reason, it doesn't work. No matter how much I increase/decrease the single passions, the court didn't sum up, and it spawn always 0. The resulting attribute were already without disabled tag.&nbsp; I'm referring to this one: const changes = arr =&gt; arr.reduce((all, one) =&gt; `${all} change:${one}`,''); const int = (score,fallback = 0) =&gt; parseInt(score) || fallback; const passions = { fidelitas: ['homa', 'loy', 'loy2', 'feal', 'duty'], fervor: ['hat', 'hat1', 'hat2', 'hat3', 'hat4', 'lov', 'lov2', 'lov3'], adoration: ['devo', 'ador'], civilitas: ['chiva', 'hos', 'stati'] }; Object.keys(passions).forEach(this_passion =&gt; { on(changes(passions[this_passion]), () =&gt; { getAttrs(passions[this_passion], values =&gt; { const total = Object.values(values).reduce((all, one) =&gt; all + int(values[one]), 0); setAttrs({ [`${this_passion}_total`]: total }); }); }); }); You mispelled 'homa' with 'home', but it wasn't that. Maybe there's an error in the last section of the code (bolded)? Does it matter that the sheet use Legacy Sanitization? The fact that the passion are in caps in HTML (HOMA) shouldn't matter, right? (it does not for the previous code). Consider that the resulting attribute should be @ {fidelitas_total} , and similar. &lt;input style='display:inline; width:30px; font-color:red;' type="text" class="sheet-carac2" name=" attr_civilitas_total " value="0" readonly&gt;
1720214008

Edited 1720223442
GiGs
Pro
Sheet Author
API Scripter
I'll have a look to see why it's not working. I'll be creating my own html, which might not be the same as yours. Can you post the HTML you use (just the passion attributes and the totals) Legacy Santitisation shouldn't matter (but you should move away from that if possible, for other reasons). This [`${this_passion}_total`] should create a name like civilitas_total so that shouldn't be the issue. There's likely a subtle syntax error in the code that I'll find when I test it.
1720215240

Edited 1720226849
GiGs
Pro
Sheet Author
API Scripter
Aha, I see the error, and it was an error I made - the kind of thing that happens when you dont test complex code. Replace this line const total = Object.values(values).reduce((all, one) =&gt; all + int(values[one]), 0); with this one const total = Object . values ( values ). reduce (( all , one ) =&gt; all + Math.min(20, Number ( one )), 0 ); or const total = Object . values ( values ). reduce (( all , one ) =&gt; all + Math.min(20, int ( one )), 0 ); There are number of ways to resolve this - one of the things you learn in programming, there's usually several ways to do something. I'll explain what happened here, but it depends on several advanced features so don't worry if you don't follow perfectly. PS: I just realised I forgot to limit each passion to 20, the reason for this project lol, and have just added in the Math.min function to do that. One of the advantages of loops like reduce and forEach is you can do something on each iteration of the loop - like limiting the current score ( one ) to 20.
1720216038

Edited 1720291923
GiGs
Pro
Sheet Author
API Scripter
You can use the Object, erm, object to create an array of all items in that object. (The word object has a specific meaning in JavaScript.) Look at the getAttrs line&nbsp; - we create an object called values , and that includes each attribute's name and its value. In fact, it will look something like this: values = {homa: "17", loy: "13", loy2: "23"} and might have more things in it. The thing surrounded by curly brackets {} is an Object. Thats the wrong format for what we need. Object.values creates an array, a list, of just the values of this object, confusingly also called values (they aren't related). So it would create something like Object.values(values) = ["17", "13", "23"] Now we work with the reduce function, where things get even more confusing. At base, this is a function that loops through an array and does something. So when we have Object . values ( values ). reduce (( all , one ) =&gt; all + Number ( one ), 0 ); we really have ["17", "13", "23"] . reduce (( all , one ) =&gt; all + Number ( one ), 0 ); Now, look at the reduce function: reduce((all, one) =&gt; all + Number(one), 0) It takes an array, and iterates through it, one by one. In each loop, "one" becomes the current value, and "all" is the ongoing total. You can see this is just converting each item in the array to a number, then adding it to the running total. The bit at the end: , 0) tells you your starting total. Reduce can be used for a lot of things (its mind boggling what you can do with this function - see also my changes function), and it's often handy to know your starting point - here it is a total of 0. The error: I had made the mistake of including int(values[one]) , which is normally useful. I usually used values[name] to get the score, but with Object.values(values) I already had all the scores and didn't need to use that approach. You do need to use a function like Number because Roll20 stores values as strings (words) not numbers, and JavaScript can work weirdly with numbers in string form, so this function now works properly. Note that you have to be careful to ensure your passions don't contain words. A safer version of that worker that properly handles players entering a typo would look like this (using the user-created num function, instead of the standard Number function).: const changes = arr =&gt; arr . reduce (( all , one ) =&gt; `change: ${ one } ${ all } ` , '' ); &nbsp; &nbsp; const num = ( score , fallback = 0 ) =&gt; Number ( score ) || fallback ; &nbsp; &nbsp; const passions = { &nbsp; &nbsp; &nbsp; &nbsp; fidelitas: [ 'homa' , 'loy' , 'loy2' , 'feal' , 'duty' ], &nbsp; &nbsp; &nbsp; &nbsp; fervor: [ 'hat' , 'hat1' , 'hat2' , 'hat3' , 'hat4' , 'lov' , 'lov2' , 'lov3' ], &nbsp; &nbsp; &nbsp; &nbsp; adoration: [ 'devo' , 'ador' ], &nbsp; &nbsp; &nbsp; &nbsp; civilitas: [ 'chiva' , 'hos' , 'stati' ] &nbsp; &nbsp; }; &nbsp; &nbsp; Object . keys ( passions ). forEach ( this_passion =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on ( changes ( passions [ this_passion ]), () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ( passions [ this_passion ], values =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const total = Object . values ( values ). reduce (( all , one ) =&gt; all + Math.min(20, num ( one )), 0 ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [ ` ${ this_passion } _total` ]: 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; &nbsp; &nbsp; }); &nbsp; &nbsp; });
1720217739

Edited 1720292280
GiGs
Pro
Sheet Author
API Scripter
PS: the code was working, it was just doing it wrong. In this line: const total = Object.values(values).reduce((all, one) =&gt; all + int(values[one]), 0); the quantity values[one] was not being found and so returning an error, and when int(values[one]) returns an error, it gives a value of 0. (Usually, this is good, but not here.) So every attribute was being treated as zero, and the total was always being calculated as zero. Oops. You can test this by setting the default value in one of your totals, like value="10", now change the value of one of the passions that contribute to that total. The total is now overwritten and becomes zero.
1720301575

Edited 1720301602
Ok, checked you last version, and it works for me too! I'm speaking of courts, for pezzidiferro I have to wait a little bit more. Anyway, when I'll have test it, I'll tell you. As for the strings, there's no worry, at least in game, since the various passions value's attributes are set to delete any value that isn't a number and turn it to zero. Anyway, one more time, thank you a lot GiGs!
Ok, got some time and tried out the 'pezzidiferro' code. It works perfectly. I just add a " *-1 " to make it negative instead of positive. on('change:pezzidiferro', () =&gt; { getAttrs(['pezzidiferro'], values =&gt; { const pezzidiferro = parseInt(values.pezzidiferro) || 0; const pezzidiferro_malus = Math.floor(pezzidiferro / 3) *-1 ; setAttrs({ pezzidiferro_malus: pezzidiferro_malus }); }); }); Thank you so much again, GiGs!
1721243869
GiGs
Pro
Sheet Author
API Scripter
Thats great. You can also just put a - sign at the start of the Math object like this on('change:pezzidiferro', () =&gt; { getAttrs(['pezzidiferro'], values =&gt; { const pezzidiferro = parseInt(values.pezzidiferro) || 0; const pezzidiferro_malus = -Math.floor(pezzidiferro / 3); setAttrs({ pezzidiferro_malus: pezzidiferro_malus }); }); });
Good to know! Thank you again!