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 calculate attribute modifier that doesn't follow a fixed formula

How can I calculate attribute modifiers that don't follow a fixed formula? The attributes in my system are Strength, Constitution, Dexterity, Agility, Intelligence, Perception, Will and Charisma. The rules are: - if the attribute value goes from 1 to 2, then the modifier is -3 - if the attribute value goes from 3 to 4, then the modifier is -2 - if the attribute value is from 5 to 8, then the modifier is -1 - if the attribute value is from 9 to 14, then the modifier is 0 - if the attribute value is 15 or more, then the modifier is (attribute value-14) / 2
1596918182
Kraynic
Pro
Sheet Author
If this is for a sheetworker, you just need to use if, else if, to get the different bands.  Or at least that was the way one set of derived stats is figured in my sheets.  You just add in as many steps as needed. on("change:me sheet:opened", function () { getAttrs(['me'], function (values) { const mep = parseInt(values['me'])||0; const mei = parseInt(values['me'])||0; let me_psionic = 0; if (mep > 30) me_psionic = 8; else if (mep > 15) me_psionic = Math.round((mep -15)*.5); let me_insanity = 0; if (mei > 30) me_insanity = 13; else if (mei > 20) me_insanity = Math.round(mei -17); else if (mei > 15) me_insanity = Math.round((mei-15)*.5); setAttrs({ me_psionic: me_psionic, me_insanity: me_insanity }); }); });
1596918845
GiGs
Pro
Sheet Author
API Scripter
Figure out what the maximum possibe attribute can be and build a group roll like this, going up to the maximum possible attribute: /roll {1, 3, 5, 9, 15, 17, 19, 21, 23, 25, 27, 29, 31}<@{strength} -4 If you use a Macros character, you could save that group roll in one place and refer it in other macros, like /roll @{Macros|Modifiers}<@{strength} -4 with Macros|Modifiers being  {1, 3, 5, 9, 15, 17, 19, 21, 23, 25, 27, 29, 31} Of course you could save it as a Macro, too, and call it /roll #Modifiers<@{strength} -4 The -4 at the end is the bit that normalises things so 9-14 give a 0 modifier.
1596919930

Edited 1596922254
GiGs
Pro
Sheet Author
API Scripter
Oh, I missed this was in the character sheet forum. Here's a sheetworker using the findIndex function, which is excellent for situations like this: const getStatModifier = stat => stat > 14 ? Math.ceil((stat-14)/2) : [1, 3, 5, 9, 15].findIndex(n => n > stat ) -4; on('change:strength', () => {     getAttrs(['strength'], v => {         const score = parseInt(v.strength) || 0;         const modifier = getStatModifier(score);         setAttrs({             strength_mod: modifier         });     }); }); If I know what your attribute names on the sheet were, I could post this is a single forEach loop for all stats. The getStatModifier is a function - you call it in each of your stat workers like the above. Note that it treats stats of 0 or below the same as having a -4 modifier.
The attributes are: For Con Des Agi Int Per Von Car The modifiers are: For-mod Con-mod Des-mod Agi-mod Int-mod Per-mod Von-mod Car-mod
1596922224

Edited 1596922291
GiGs
Pro
Sheet Author
API Scripter
That's an excellent naming scheme, it makes this kind of operation easy. Barring any silly typos on my part, this should handle it. const getStatModifier = stat => stat > 14 ? Math.ceil((stat-14)/2) : [1, 3, 5, 9, 15].findIndex(n => n > stat ) -4; const stats = ['For', 'Con', 'Des', 'Agi', 'Int', 'Per', 'Von', 'Car']; stats.forEach(stat => {     on(`change:${stat.toLowerCase()}`, () => {         getAttrs([stat], v => {             const score = parseInt(v[stat]) || 0;             const modifier = getStatModifier(score);             setAttrs({                 [`${stat}-mod`]: modifier             });         });     }); });
1596923933

Edited 1596923979
GiGs
Pro
Sheet Author
API Scripter
Kraynic in your sheet worker, at the top you set mep and mei as constants with both the same value. You could just set a me  value with that score, and use it instead of either of those, like so: on("change:me sheet:opened", function () {     getAttrs(['me'], function (values) {         const me = parseInt(values['me'])||0;         let me_psionic = 0;         if (me > 30) me_psionic = 8;         else if (me > 15) me_psionic = Math.round((me -15)*.5);         let me_insanity = 0;         if (me > 30) me_insanity = 13;         else if (me > 20) me_insanity = Math.round(me -17);         else if (me > 15) me_insanity = Math.round((me-15)*.5);         setAttrs({             me_psionic: me_psionic,             me_insanity: me_insanity         });     }); }); If you;re interested in how to streamline things further with the ternary operator I know you've seen me mention many times, I'll do that, but first, lets handle the first if statement in a different way. You have this:   if (me > 30) me_psionic = 8;  else if (me > 15) me_psionic = Math.round((me -15)*.5); The max value can also be set this way: me_psionic = Math.min(8,  Math.round((me -15)*.5)); Math.min is just like the roll20 keep-lowest or drop-highest operators. So if the calculated value is above 8, it gets set as 8. There's also Math.max, which returns the highest value, you you could use that to get rid of any values below 0. Like this: const me_psionic = Math.max(0, Math.min(8, Math.round((me -15) * 0.5)); There, the whole calculation is handled without an if statement. But I said we'd be using ternary operators. Your second calculation is a touch more complex. So we do this: const me_insanity = (me > 20) ? Math.min(13, Math.round(me -17)) : Math.max(0, Math.round((me-15)*.5)); Notice we are using the same technique, but split into two sections. If me > 20, we use the larger calculation: Math . min ( 13 ,  Math . round ( me  - 17 )) which is basically doing, in roll20 terms: { @{me}-17, 13}dh1 The else  section (if me not >20), handles the 0 part: Math . max ( 0 ,  Math . round (( me - 15 )* .5 )) which in roll20 terms is { round((@{me}-15)/2), 0}kh1 Putting all that together, we get: on("change:me sheet:opened", function () {     getAttrs(['me'], function (values) {         const me = parseInt(values['me'])||0;         const me_psionic = Math.max(0, Math.min(8, Math.round((me -15) * 0.5));         const me_insanity = (me > 20) ? Math.min(13, Math.round(me -17)) : Math.max(0, Math.round((me-15)*.5));         setAttrs({             me_psionic: me_psionic,             me_insanity: me_insanity         });     }); });
GiGs always saving my games and the fun of my group! The script didn't work, is it due to "toLowerCase"? the names of attributes and modifiers are capitalized in the code. Thank you also Kraynic.
1596926167
GiGs
Pro
Sheet Author
API Scripter
It should be working. Are your Von-mod, Con-mod, etc stats disabled? can you post the html for those? Regarding toLowerCase() - Attributes on the on('change') line must be in all lower case, or they will fail unpredictably. so toLowerCase() function is used to make sure it is in lower case on that line.  Come to think of it, since roll20 is case-insenstive everywhere but there, you could change it to: const getStatModifier = stat => stat > 14 ? Math.ceil((stat-14)/2) : [1, 3, 5, 9, 15].findIndex(n => n > stat ) -4; const stats = ['for', 'con', 'des', 'agi', 'int', 'per', 'von', 'car']; stats.forEach(stat => {     on(`change:${stat} sheet:opened`, () => {         getAttrs([stat], v => {             const score = parseInt(v[stat]) || 0;             const modifier = getStatModifier(score);             setAttrs({                 [`${stat}-mod`]: modifier             });         });     }); }); But that just simplifies the code - it doesnt make any difference to the issue you are having.
Hi GiGs, I tried not to disturb you but I couldn't! The doubt that arose, I realized that I need a third column in the attributes that indicates the sum of the original attribute with any temporary value, being: attribute_name_ori and attribute_name_temp and attribute_name_total respectively. How can I change the script to sum up these two attributes, calculate the total and then define the modifier by that total? In the example below I used the "forca" attribute (for_ori, for_temp and for_total) <input type="text" name="forca" style="width:6em;" value="FOR"/> <input type="number" name="attr_For_ori" style="background-color:lightblue" value="0"/> <input type="number" name="attr_For_Temp" value="0"/> <input type="number" width=3 style="width: 3.5em;" name="attr_For" value="0" readonly/> <input type="number" width=3 style="width: 3.5em;" name="attr_For_Mod" readonly /> <input type="number" width=4 style="background-color:lightblue; width: 4.1em;" name="attr_For-Teste" value="(@{For}*4)" disabled />
1597159266
GiGs
Pro
Sheet Author
API Scripter
You're never disturbing me if you're giving me the opportunity to post sheet workers, lol. I assume the previous code worked properly. If so, you can do this: const stats = ['for', 'con', 'des', 'agi', 'int', 'per', 'von', 'car']; stats.forEach(stat => {     on(`change:${stat}_ori change:${stat}_temp sheet:opened`, () => {         getAttrs([`${stat}_ori`, `${stat}_temp`], v => {             const ori = parseInt(v[`${stat}_ori`]) || 0;             const temp = parseInt(v[`${stat}_temp`]) || 0;             const score = ori + temp;             const modifier = getStatModifier(score);             const teste = score * 4;             setAttrs({                 [stat]: score,                 [`${stat}-mod`]: modifier,                 [`${stat}_teste`]: teste             });         });     }); }); This also calculates your For-Teste attribute - so you'll want to change that input from disabled to readonly: <input type="number" width=4 style="background-color:lightblue; width: 4.1em;" name="attr_For-Teste" value="0" readonly /> I also added sheet:opened  to the event, so stats will be calculated automatically when you open the sheet.
it worked brilliantly! Thank you one more time!!!
1597237776
GiGs
Pro
Sheet Author
API Scripter
great :)