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

Calculating single stat bonuses from non-uniformly incremented tables

1554045016

Edited 1554045488
Hey again, I'm working on my V&V 2.0 custom sheet, as a newb to CSS, HTML and js.  I've gotten some great help so far, and my confidence has increased a lot, until... I ran into this particular issue which I thought I understood how to tackle, but now realize I am lost. I'm trying to calculate 2 bonuses based on attr_charisma. One is for reactions from good (attr_good), the other Reactions from evil (attr_evil).  The good bonus ranges from negative charisma sores (charisma <0 = -8) increasing in bands of three (charisma > 0 = -6, charisma > 3 = -4, etc) and continues until hit higher charisma scores with positive bonuses. The banding changes to every five points at some point, though so it's not consistent.  To make matters more complicated, the evil bonus is a negative version of the good bonus... so all negative bonuses to good are positive bonuses to evil and vice versa.  I checked the community stuff  and saw some examples GiGs  and Diane posted, and I think I understand what  I need to do to make this work, but for whatever reason I can't seem to get it to function at all. Here is the input HTML: < label   style = "width:1600px" > Reactions From: < / label >< br   / >   < h4 > Good: < / h4 >< input   type = "text"   name = 'attr_good'   readonly   class = 'sheet-hitmod' >   < h5 > Evil: < / h5 >< input   type = "text"   name = 'attr_evil'   readonly   class = 'sheet-hitmod' >< br   / > Then here is the sheetworkers I have set  up that  I did my best to follow and adapt Diane's example that GiGs recommended: < script type = "text/worker" > function  charismaMod ( )   {         let bonus  =   0 ;          if   ( charisma  <   0 )  bonus  =   - 8 ;          else   if   ( charisma  >   0 )  bonus  =   - 6 ;          else   if   ( charisma  >   2 )  bonus  =   - 4 ;          else   if   ( charisma  >   5 )  bonus  =   - 2 ;          else   if   ( charisma  >   8 )  bonus  =   0 ;          else   if   ( charisma  >   11 )  bonus  =   + 1 ;          else   if   ( charisma  >   14 )  bonus  =   + 2 ;          else   if   ( charisma  >   17 )  bonus  =   + 3 ;          else   if   ( charisma  >   20 )  bonus  =   + 4 ;          else   if   ( charisma  >   25 )  bonus  =   + 5 ;          else   if   ( charisma  >   30 )  bonus  =   + 6 ;          else   if   ( charisma  >   35 )  bonus  =   + 7 ;          else   if   ( charisma  >   40 )  bonus  =   + 8 ;          else   if   ( charisma  >   45 )  bonus  =   + 9 ;          else   if   ( charisma  >   50 )  bonus  =   + 10 ;          else   if   ( charisma  >   55 )  bonus  =   + 11 ;          else   if   ( charisma  >   60 )  bonus  =   + 12 ;          else   if   ( charisma  >   65 )  bonus  =   + 13 ;          else   if   ( charisma  >   70 )  bonus  =   + 14 ;          else   if   ( charisma  >   75 )  bonus  =   + 15 ;          else   if   ( charisma  >   80 )  bonus  =   + 16 ;          else   if   ( charisma  >   85 )  bonus  =   + 17 ;          else   if   ( charisma  >   90 )  bonus  =   + 18 ;          return  bonus ;         } </ script >        < script type = "text/worker" > on ( "change:charisma sheet:opened" ,   function ( )   {        getAttrs ( [ "charisma" , "bonus" , "good" , "evil" ] ,   function ( values )   {         let charisma  =  parseFloat ( values. charisma , 10 ) || 1 ;         let bonus  =  parseFloat ( values. bonus , 10 ) || 1 ;         let good  =  bonus ;         let evil  =   Math . round ( good ) *- 1 ;         setAttrs ( {                                          good :  good             evil :  evil          } ) ;      } ) ; } )   </ script >   As always, any help is  appreciated.... 
1554048994

Edited 1554050655
GiGs
Pro
Sheet Author
API Scripter
The first thing I notice is you have each function inside its own script block. You should only have one <script> </script> block, and all your workers go inside there. Minor point: I'd recommend changing good and evil inputs to numbers, change this: < input   type = "text"   name = 'attr_good'   readonly   class = 'sheet-hitmod' > To this: < input   type = "number"   name = 'attr_good'   readonly   class = 'sheet-hitmod' > Okay on to the main thing: you dont call the charisma function in your worker. My guess is this: let bonus  =  parseFloat ( values. bonus , 10 ) || 1 ; should be  let bonus  =  charismaMod(charisma); There's a couple of mistakes in your charismaMod function. The first, is it doesant have any way to get the charisma score. When making functions like this, you pass it scores it needs to use like this: function  charismaMod (charisma )   { It can now use the charisma score you send it. Also, your charisma function has the signs reversed. An if statement will stop at the first true value. Imagine you have a charisma of 60. When this line is reached:   else   if   ( charisma  >   0 )  bonus  =   - 6 ; the function will stop, since 60 is greater than 0, and it will return a bonus of -6. You should rewrite the function in one of two ways:   if   ( charisma  <   0 )  bonus  =   - 8 ;          else   if   ( charisma  <=   2 )  bonus  =   - 6 ;          else   if   ( charisma  <=   5 )  bonus  =   - 4 ; and so on, or reverse the order:   if   ( charisma  >90)  bonus  =   18 ; else   if   ( charisma  >   85 )  bonus  =  17; else   if   ( charisma  >   80 )  bonus  =  16; and so on. Also dont put a + before the positive values. two more things: In your getAttrs line you have bonus, good, and evil, but you dont actually read those values from the character sheet so you dont need them there. This: getAttrs ( [ "charisma" , "bonus" , "good" , "evil" ] , should be getAttrs ( [ "charisma" ] , That's the only attribute you call from the character sheet, everythign else is calculated inside the sheetworker. Secondly, this  let charisma  =  parseFloat ( values. charisma , 10 ) || 1 ; The ||1 at the end is a default value. If the calculation in parseFloat is invalid, it will return an error. By putting ||1 you are saying, "in case of error, replace the error with the number 1." In other words, set a default value of 1. My guess is you want a default value of 0, so that would be ||0 instead. Putting this altogether, your script should look like this: < script type = "text/worker" > function  charismaMod (charisma )   {         let bonus  =   0 ;         // correct the if entries as described above.          return  bonus ;         }; on ( "change:charisma sheet:opened" ,   function ( )   {        getAttrs ( [ "charisma" ] ,   function ( values )   {         let charisma  =  parseFloat ( values. charisma , 10 ) || 1 ;         let good  =  charismaMod(charisma)||0 ;         let evil  =  -good ;         setAttrs ( {                                          good :  good             evil :  evil          } ) ;      } ) ; } );   </ script > I also tweaked the syntax of the let evil = line. I also noticed you never use the bonus for anything other than the good, so you dont need to define a separate bonus variable. Also added a semi-colon at the end of each function. Not always needed, but it's safer to have it there for when it is needed. Note: is charisma always a whole number? If its never fractional or decimal, you should use parseInt instead of parseFloat. Same syntax, just change that one word. Some people would suggest compressing that even further, like say: on ( "change:charisma sheet:opened" ,   function ( )   {        getAttrs ( [ "charisma" ] ,   function ( values )   {         let charisma  =  parseFloat ( values. charisma , 10 ) || 1 ;         let good  =  charismaMod(charisma)||0 ;         setAttrs ( {                                          good :  good             evil :  -good          } ) ;      } ) ; } )   But I think its good practice when starting out to define all variables before the setAttrs, so when you have problems you can do error checking on those values. 
1554051355

Edited 1554051619
GiGs
Pro
Sheet Author
API Scripter
I noticed that if statement can be simplified: function charismaMod(charisma) {     let bonus = 0; if (charisma > 90) bonus = 18; else if (charisma > 20) bonus = Math.ceil(charisma/5) -1; else if (charisma > 11) bonus = Math.floor(charisma/3) -3; else if (charisma > -1) bonus = Math.floor(charisma/3) *2 -6; else bonus = -8;     return bonus;   } This is accurate if the original if statement is correct. I;m wondering if lines like this else   if   ( charisma  >   20 )  bonus  =   + 4 ; should be else   if   ( charisma >=   20 )  bonus  =   + 4 ; The error I just noticed in the original if statement makes me wonder about this: if   ( charisma  <   0 )  bonus  =   - 8 ;  else   if   ( charisma  >   0 )  bonus  =   - 6 ; In this, if charisma equals exactly zero, it will not be caught by the above. 0 is not below 0, or above zero, so bonus would be undefined. In Sheet workers, you need to use >= if you mean "greater than or equal to". It's not the same as roll20 dice macros. If all those > should have been >= the above function would instead be function charismaMod(charisma) {     let bonus = 0; if (charisma > 90) bonus = 18; else if (charisma >= 20) bonus = Math.floor(charisma/5); else if (charisma >= 11) bonus = Math.round(charisma/3) -3; else if (charisma >= 0) bonus = Math.round(charisma/3) *2 -6; else bonus = -8;     return bonus;   }
1554058800
vÍnce
Pro
Sheet Author
You rock GiGs. Just saying. ;-)
1554062585
Finderski
Plus
Sheet Author
Compendium Curator
As usual, Vince is spot on... :)
Holy Shitaki Mushrooms.... GiGs, you are a person among people.  So I realized, (a bit late) that I had been bouncing back and forth a lot between names for variables at like 4 am. And it seems I  undid some things that were correct before I gave up and posted. I think that might explain why my variables may have been confused. The thing I did not even think about was to construct a formula for the bonuses in each different band.  And yeah, I really messed up my general coding etiquette.  I'm going to ask need to ask how you came up with the formulas though?  One they're brilliant, but it seems the calculation might be off by one. I did 45 Charisma and got a 9 bonus by the math you gave, but the chart says 8. Am I just missing something? This is the original source I am referring to. I'm not concerned about evil characters or neutral at the moment, just the Good Character portion.    Thank you, thank you, and thank you once more for spending so much time on this. I truly appreciate it.
1554072196

Edited 1554075051
Updated: I put the edited workers in as per GiGs last iteration above and for some reason all of the other workers I am using are fine, but the good and evil input "number" fields are blank. Also it seems, if I am correct, that based on the chart, that maybe this should be correct? function charismaMod(charisma) {     let bonus = 0; if (charisma > 90) bonus = 18; else if (charisma > 20) bonus = Math.floor(charisma/5); else if (charisma > 11) bonus = Math.round(charisma/3) -3; else if (charisma >= 0) bonus = Math.round(charisma/3) *2 -6; else bonus = -8;     return bonus;   } I reviewed the other HTML and nothing is different that I can spot, but for whatever reason, I can't get any value to print for good or evil.
1554091273

Edited 1554093908
GiGs
Pro
Sheet Author
API Scripter
Hey guys, thanks all. messages like those are great to wake up to. Johnny, I just noticed the problem with the sheet worker. I failed to catch a missing comma earlier: on("change:charisma sheet:opened", function() {       getAttrs(["charisma"], function(values) {         let charisma = parseFloat(values.charisma,10)||0;         let good = charismaMod(charisma)||0;         setAttrs({                                         good: good, // i missed the comma here             evil: -good         });     }); When you have multiple attributes to set, you need to separate each one with a comma. Hopefully this version works! hehe. About the charisma calculations - based on the chart, the first version of the function is the correct one. i shouldn't have second-guessed your if statement :) This is the correct one: function charismaMod(charisma) {     let bonus = 0; if (charisma > 90) bonus = 18; else if (charisma > 20) bonus = Math.ceil(charisma/5) -1; else if (charisma > 9) bonus = Math.floor(charisma/3) -3; else if (charisma >= 0) bonus = Math.floor(charisma/3) *2 -6; else bonus = -8;     return bonus;   } (I slipped in a >=0 instead of > -1 at the bottom just because it looks neater, i think.) With this a charisma of 45 would give a bonus of 8. In case you aren't familiar with those functions, Math.ceil rounds up, Math.floor rounds down.
1554092072

Edited 1554094005
GiGs
Pro
Sheet Author
API Scripter
I'm going to ask need to ask how you came up with the formulas though?   This is just a matter of being able to notice patterns, and having a math background. I noticed that at the top end the bonus went up for each 5 points of charisma, so I knew we'd be able to calculate the bonus there by dividing by 5, and then adding a correction bonus to make it match. Since 40/5 is 8, and the listed bonus there is 7, the correction had to be -1. That gave us  bonus = Math.ceil(charisma/5) -1 . The important trick is figuring out which rounding function to use. In this case, since 36/5 = 7.2, and 35/5 = 7, and they gave different bonuses, I knew we had to round up, so Math.ceil() was the function to use. Likewise the lower bonuses were all in groups of 3 charisma points, so dividing by 3 was necessary. Same process - just figuring out which rounding function to use, and what correction to use to get the correct bonus. Here 12/3 = 4, the bonus at 12 is +1, the difference between those values is 3, so our correction bonus is -3. hence bonus = Math.floor(charisma/3) -3. And the bonuses below 0, where it dropped by 2 each time, was simply recognising the need to divide by 3, and then double it to keep the -2 per step. Whenever you see a repeated pattern you can use a process like this. Then you use the if statement to split the table into the different repeated groups, and calculate each repeated group as a single step. Sometimes the calculation is a bit more complicated (the ranges don't always round neatly as they do in this example), and that requires an extra step, but it's always do-able.  Hope this answers your question :)
1554093002

Edited 1554093542
GiGs
Pro
Sheet Author
API Scripter
Oh, one final bit of advice for constructing expressions like this. If your are comparing using > or >= (greater than), always have your biggest numbers at the start, and go in order of big to small. If comparing with < or <= (less than), always have your lowest numbers at the start, and go in order of small to big. This is because, as i mentioned earlier, the function will stop at the first matching result. So if I had done it in the wrong order: if (charisma >= 0) bonus = Math.floor(charisma/3) *2 -6; else if (charisma > 9) bonus = Math.floor(charisma/3) -3; else if (charisma > 20) bonus = Math.ceil(charisma/5) -1; else if (charisma > 90) bonus = 18; else bonus = -8;  And it checked with a charisma of 45, it would give the drastically wrong result of 24. This is because a 45 charisma matches the first result (>=0). It matches the first 3 rows, in fact, but it stops at the first one and doesn't bother to continue down to the proper row. So it calculates 45/3 * 2 -6 = 24. Errors like this can often be tricky to spot if you dont know what you're looking for. It is returning a value, but it's wildly, hilariously incorrect. So it's always important in programming to think through the process, and make sure you are doing things in the necessary order. And also watch out when using > and < together, like that original example: if (charisma < 0) bonus = -8; else if (charisma > 0) bonus = -6; This is honestly one of the easiest mistakes to make, I've done it myself countless times. When you switch from > to < or vice-versa in the same function, you have to be careful to avoid gaps. You'd change the above to one of these: if (charisma < 0) bonus = -8; else if (charisma > -1) bonus = -6; or if (charisma < 0) bonus = -8; else if (charisma >= 0) bonus = -6; Just to make sure you catch the in-between value.
GiGs-buddy... I can't thank you enough. Love the formula shortcuts! Seeing done backwards in your example makes me want to kick myself for not having seen it all along. You'd make a hell of systems designer in the game  industry, buddy. I work on the content and creative side of games, so I occasionally write (steal/borrow/beg for help with) some scripts for that, but my real foundation is  creative stuff like writing, narrative, and cinematic design. Mostly I use  the nice visually accessible tools that my tools engineer makes to make cool stuff and do my job. I fake it  until it works most times and the run screaming for help. Sounds familiar right?.  Thank you yet again for the time and for actually making this easy enough to grok that I don't lose interest! You rock, man.
So just a quick update: That didn't do the trick either. I reviewed all the HTML to the best of my means and the workers too, but for whatever reason it won't populate he good and evil inputs.  Not sure why this would be.They are set to Readonly which shouldn't be a problem. I tried t This is my (*your) final product: As always any suggestions are welcome. Thanks! < script type = "text/worker" >   function  charismaMod ( charisma )   {     let bonus  =   0 ;      if   ( charisma  >   90 )  bonus  =   18 ;      else   if   ( charisma  >   20 )  bonus  =   Math . ceil ( charisma / 5 )   - 1 ;      else   if   ( charisma  >   9 )  bonus  =   Math . floor ( charisma / 3 )   - 3 ;      else   if   ( charisma  >=   0 )  bonus  =   Math . floor ( charisma / 3 )   * 2   - 6 ;      else  bonus  =   - 8 ;      return  bonus ;    }     on ( "change:charisma sheet:opened" ,   function ( )   {        getAttrs ( [ "charisma" ] ,   function ( values )   {         let charisma  =  parseFloat ( values. charisma , 10 ) || 0 ;         let good  =  charismaMod ( charisma ) || 0 ;         setAttrs ( {                                          good :  good ,             evil :   - good          } ) ;      } ) ;
1554101419

Edited 1554101648
GiGs
Pro
Sheet Author
API Scripter
I'm not sure if this is a copy paste error, but the code above is missing a final   } ) ; and should be function  charismaMod ( charisma )   {     let bonus  =   0 ;      if   ( charisma  >   90 )  bonus  =   18 ;      else   if   ( charisma  >   20 )  bonus  =   Math . ceil ( charisma / 5 )   - 1 ;      else   if   ( charisma  >   9 )  bonus  =   Math . floor ( charisma / 3 )   - 3 ;      else   if   ( charisma  >=   0 )  bonus  =   Math . floor ( charisma / 3 )   * 2   - 6 ;      else  bonus  =   - 8 ;      return  bonus ;    };     on ( "change:charisma sheet:opened" ,   function ( )   {        getAttrs ( [ "charisma" ] ,   function ( values )   {         let charisma  =  parseFloat ( values. charisma , 10 ) || 0 ;         let good  =  charismaMod ( charisma ) || 0 ;         setAttrs ( {                                          good :  good ,             evil :   - good          } ) ;      } ) ; }); If that still doesn't work, could you post your entire script block - all scripts, everything from the first <script line to the last </script> Just in case there's some subtle interaction there that's messing things up.  You're right readonly shouldn't be a problem. In fact you usually want inputs that are meant to be adjusted by sheetworkers to be readonly.
1554101712

Edited 1554101812
GiGs
Pro
Sheet Author
API Scripter
PS: I've gone ahead and tested the above code with a sheet that just has the inputs for charisma, good, and evil, and it's working for me. So if it's still not working for you, we'll have to do a bit of digging through the other scripts and html to find out where exactly the problem is occurring.