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

Script worker - help debugging a nested for loop

1582093884

Edited 1582094034
Ok so the goal of this script worker is to calculate how much experience has been spent in our homebrew game. The system of the game is you spend experience to raise the attributes in the following pattern (starting at 10 in each attribute): 10 experience to get to 20, 20 experience to get to 30, 30 experience to get to 40, etc. As you can imagine doing this by hand is pretty time consuming, and it's hard to check if characters are spending an appropriate amount of experience. So I want to calculate the experience cost for each of the 5 attributes (strength, vitality, agility, finesse, and spirit) and add them together into a total in the spentexp container. So we wrote the for loop out below in mathlabs (the math works in mathlabs), converted it to javascript, and plugged it in but it fails to update. I've been trying for about a week and a half to get it to kick anything but I've hit a wall and can't figure out what is wrong with it. Any help would be awesome, I'm still learning roll20 too so feedback/guidance is also appreciated. <!-- Experience containers --> <input type="number" name='attr_experience' value='10'> <span name='attr_spentexp'></span> <input type="number" name='attr_remainingexperience' value='(@{experience}-@{spentexperience})' disabled='true'> <!-- Attributes --> Might <input type="number" name='attr_might' value='10'> <br> Vitality <input type="number" name='attr_vitality' value='10'> <br> Agility <input type="number" name='attr_agility' value='10'> <br> Finesse <input type="number" name='attr_finesse' value='10'> <br> Spirit <input type="number" name='attr_spirit' value='10'>     <script type="text/worker"> // EXPERIENCE CALCULATOR WIP const stats = ['might','vitality','agility','finesse','spirit']; stats.forEach(function (stat) {     on("change:" + stat + " sheet:opened", function () {         getAttrs([stat], function (values) { //This gets the value of the attribute's score.             var stat_score = parseInt(values[stat], 10)||0;             var exp_total = 0;                 for(var x = 1; x < 5; x++) { //Increments the cost of raising the attribute's score.                     var y = stat_score;                     var n = 0;                     for(var i=1; i < y/10; i++) {                         for(var j=1; j < 10 ; j++) {                             exp_total += n ; //Adds all of the experience together, excluding the remainder.                         };                     n += 1;                         };                 exp_total += ((y- (n*10)) * n); //Adds the remainder from the singles digit to the cost. (If a stat has a value of 96, this adds the cost of the 6.)                 };             });         });       setAttrs({         "spentexp" : exp_total     }); }); </script>
1582096640

Edited 1582109037
1. I believe you want your setAttrs function to be inside the getAttrs function within the onChange event. Without that, your not setting any attributes or doing anything with exp_total when the events fire. 2. I'm also new at Javascript, so anybody can feel free to correct me if I'm wrong but I think you'll run into async issues with the var keyword in the for loops if you don't use the let keyword to isolate the loop iterations from conflicting with each other. stats.forEach(function (stat) { on("change:" + stat + " sheet:opened", function () { getAttrs([stat], function (values) { //This gets the value of the attribute's score. var stat_score = parseInt(values[stat], 10)||0; var exp_total = 0; for(let x = 1; x < 5; x++) { //Increments the cost of raising the attribute's score. let y = stat_score; let n = 0; for(let i=1; i < y/10; i++) { for(let j=1; j < 10 ; j++) { exp_total += n ; //Adds all of the experience together, excluding the remainder. }; n += 1; }; exp_total += ((y- (n*10)) * n); //Adds the remainder from the singles digit to the cost. (If a stat has a value of 96, this adds the cost of the 6.) }; setAttrs({ "spentexp" : exp_total }); }); }); });
1582098809

Edited 1582098870
GiGs
Pro
Sheet Author
API Scripter
Your sheet worker isnt constructed correctly. Your setAttrs function is outside the change event. At the point, the context of the change event has been lost, and setAttrs cannot do anything. setAttrs and getAttrs are asynchronous functions: because of the problems of contacting a server to retrieve or update information, they dont necessarily run in the order you expect - so you have to construct your functions in a specific format. getAttrs functions must be inside a change event, and if the setAttrs depends on information grabbed from the character sheet, it must be inside the getAttrs function. This means you cant construct the function in the way you have. I'm a big proponent of using forEach functions (I worte the universal sheet worker page), but that only works if the workers are completely independent of each other. Here you want to grab the values from five different stats, so you must put them all in the same sheet worker, and do the loop inside that sheet worker. Here's a rewritten version of the function.  on('change:might change:vitality change:agility change:finesse change:spirit ', function() {     const stats = ['might','vitality','agility','finesse','spirit'];     getAttrs(stats, function(values) {         let exp_total = 0;         stats.forEach(function(stat) {             for(var x = 1; x < 5; x++) { //Increments the cost of raising the attribute's score.                 var y =  parseInt(values[stat])||0; // note: you dont need the ",10" part.                 var n = 0;                 for(var i=1; i < y/10; i++) {                     for(var j=1; j < 10 ; j++) {                         exp_total += n ; //Adds all of the experience together, excluding the remainder.                     }                     n += 1;                     }                 exp_total += ((y- (n*10)) * n); //Adds the remainder from the singles digit to the cost. (If a stat has a value of 96, this adds the cost of the 6.)             }         });         setAttrs({             'spentexp' : exp_total         });     }); }); Using 3 for loops to perform this calaculation seems like overkill. Is the maximum level 50, or the max cost multiplier 5? Since the cost increases by 1 every 10 steps we can calculate a score of 33 like so: [Score -10] + [Score-20] + [Score-30]  So 33 would cost 23 +13 +3 = 39. Checking with the 20, 30, and 40 scores you supplied, we get 10, 20+10 = 30, 30+20+10 = 60.  Here's an alternative form of the worker, using this method: on('change:might change:vitality change:agility change:finesse change:spirit ', function() {     const stats = ['might','vitality','agility','finesse','spirit'];     getAttrs(stats, function(values) {         var exp_total = 0;         stats.forEach(function(stat) {             const score =  parseInt(values[stat])||0;  // you dont need the ,10 radix.             const limit = Math.ceil(score/10) -1; // get the maximum multiple             // if the max multiple is 5, change above line to             // const limit = Math.min(5, Math.ceil(score/10) -1);             for(var mult = 1; mult < limit; mult++) { //loop through each multiple                 exp_total += score -10 * mult;              }         });         setAttrs({             'spentexp' : exp_total         });     }); });
1582127128

Edited 1582127432
Thank you guys for responding so fast. You guys rock. So to reiterate what I did wrong, the forEach statements only work for if each statement is independent from each other and the setAttrs has to always be inside the getAttrs function? And to answer some questions, we don't have a maximum cap for attributes (the idea for the campaign is essentially becoming demigods/dragonball characters) and I don't think the vars will cause async issues but I could be wrong. Also - love your tutorials GiGs, you should consider doing a really in-depth walkthrough someday, and roll20 needs to pay you lol.
1582131100
GiGs
Pro
Sheet Author
API Scripter
If they want to pay me, I'm game, haha. Thank you :) Hawkshot86 said: So to reiterate what I did wrong, the forEach statements only work for if each statement is independent from each other and the setAttrs has to always be inside the getAttrs function? That's correct. To be more specific: If you're doing a setAttrs operation it must  be inside the change event that triggered it. Sometimes you'll have a change event that doesnt involve getAttrs, but when there is a getAttrs, the setAttrs must be inside it. As a general tip, you should build your workers so that where possible, there is only one getAttrs and one setAttrs. Declare variables and build a collection of attributes to set, then set them all at the end of the worker in one setAttrs. This is because - being asynchronous-  they may not fire in the order you expect. They may change attributes in the wrong order which can create faults. The GURPS sheet just had an issue with this, in fact.