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

Totals updating on the change event after the change event...

(Full html source at <a href="https://github.com/drl2/roll20-character-sheets/blob/mighty_protectors/Villains%20and%20Vigilantes/VandVMightyProtectors.html" rel="nofollow">https://github.com/drl2/roll20-character-sheets/blob/mighty_protectors/Villains%20and%20Vigilantes/VandVMightyProtectors.html</a> ) (Yes, about half the sheetworker code in this thing was written by Gigs at this point :) ) My sheet has a repeating section called powers with a number of optional attributes that, along with a set of basic characteristics outside the repeater (and a 'weight' field elsewhere on the sheet),&nbsp; are the basis for a bunch of other calculations. The change events for the relevant fields are set up like this: &nbsp;&nbsp;&nbsp;&nbsp;["strength",&nbsp;&nbsp;"endurance",&nbsp;&nbsp;&nbsp;"agility",&nbsp;&nbsp;"intelligence",&nbsp;&nbsp;"cool"].forEach(stat&nbsp;=&gt;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;stat_cost&nbsp;=&nbsp;`${stat}_cost`,&nbsp;stat_adj&nbsp;=&nbsp;(stat&nbsp;==&nbsp;'cool')&nbsp;?&nbsp;`cl_adj_total`&nbsp;:&nbsp;`${stat.slice(0,2)}_adj_total`; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;on(`change:${stat_cost}&nbsp;change:${stat_adj}`,&nbsp;function()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;calcRepeating(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getAttrs([stat_cost,&nbsp;stat_adj],&nbsp;function(v)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;cost&nbsp;=&nbsp;+v[stat_cost]&nbsp;||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;adj&nbsp;=&nbsp;&nbsp;+v[stat_adj]&nbsp;||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;score&nbsp;=&nbsp;cost&nbsp;+&nbsp;adj; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAttrs({ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[`${stat}_score`]:&nbsp;score &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;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;updateFromBCs(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;on('change:repeating_powers&nbsp;remove:repeating_powers&nbsp;change:weight',&nbsp;function()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;calcRepeating(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;updateFromBCs(); &nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;function&nbsp;calcRepeating()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;repeatingSum(["abilityip_total",&nbsp;"st_adj_total",&nbsp;"en_adj_total",&nbsp;"ag_adj_total",&nbsp;"in_adj_total",&nbsp;"cl_adj_total","init_adj_total",&nbsp;"pwr_adj_total",&nbsp;"hp_adj_total",&nbsp;"luck_adj_total",&nbsp;"ability_cp_total"], &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"powers", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;["ip_cost",&nbsp;"st_adj",&nbsp;"en_adj",&nbsp;"ag_adj",&nbsp;"in_adj",&nbsp;"cl_adj",&nbsp;"init_adj","pwr_adj",&nbsp;"hp_adj",&nbsp;"luck_adj",&nbsp;"ability_cost"]); &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;function&nbsp;updateFromBCs()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getAttrs([ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // all the stuff that it needs to use &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ] ,&nbsp;function(v)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(v.ability_cp_total); // examples of lines put in to diagnose weird results with totals &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(v.luck_adj_total); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // initialize some local vars, do some calculations, set some attrs &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // all seem to work except totals are sometimes delayed &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}; If I load up the sheet and change the basic characteristics, everything works fine.&nbsp; However, if I make a change under the powers section, it doesn't get reflected immediately in the calculations.&nbsp; Instead, the next time I make a change, the results of the previous change are applied! As noted above, I put in some console output to see what was going on.&nbsp; It turns out that when the change even originates in the repeating section, repeatingSum is run but the totals aren't getting updated.&nbsp; However, the next time I make a change that triggers any of this functionality, it catches up to what the totals should have been the previous time! For instance with ability_cp_total, I fire up the sheet which has 3 items each contributing 10 points to this total for 30 total in the repeating area. Add 5 more points to one of the items.&nbsp; Console output says total is still 30. Take 2 points away.&nbsp; Console says total is now 35. Add the 2 point back.&nbsp; Total is now 33. Etc. Any thoughts on what could be causing this?&nbsp; I'm not exactly sure when it started behaving this way and I've made a fair number of changes (including the new version of repeatingSum) recently, but I wasn't seeing this delay previously.
1592879549

Edited 1592883517
GiGs
Pro
Sheet Author
API Scripter
You're almost certainly encountering issues caused by getAttrs and setAttrs being asynchronous functions. If you didnt see it before, it's because your sheet had a lot fewer attributes involved, so each worker finished too quickly for you to see the error. But it would have become apparent sooner or later. It's also dependent on roll20's server load, so it'll be more obvious when they are having more congested nights. Your repeating_powers function looks like this: on('change:repeating_powers&nbsp;remove:repeating_powers&nbsp;change:weight',&nbsp;function()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;calcRepeating(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;updateFromBCs(); }); calcRepeating includes a getAttrs and setAttrs call. So does updateFromBCs. The thing to understand is these functions dont necessarily finish in the order they are listed in the worker. getAttrs/setAttrs calls take time to complete - they have to contact roll2o's internet server. So calcRepeating starts, and while its running, the code continues to the next line. That code then starts. And it's getAttrs grabs attribute values from the sheet before calcRepeating has finished and updated them. So it's using old data. One of your other functions has calcRepeating, a separate getAttrs -&gt; stAttrs section, then updatefromBCs. That's even worse - it will have three processes running, and the updateFromBCs might finish first.&nbsp; And these functions dont include a full set of change triggers. So when attributes on the sheet are updated, they dont necessarily trigger change events to keep anything that depends on them updated. So the main problem here is that you need to set up the change lines to include all stats that a worker might want to respond to, to keep everything in sync.&nbsp; Every worker that launches UpdateFromBCS need to have all these states as change events: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"strength_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"endurance_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"agility_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"intelligence_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"cool_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"init_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"hp_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"pwr_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"luck_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"weight", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"ability_cp_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"strength_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"endurance_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"agility_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"intelligence_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"cool_cost" And every worker that launches calcRepeating must have the following attributes from the repeating section as change events: "ip_cost",&nbsp;"st_adj",&nbsp;"en_adj",&nbsp;"ag_adj",&nbsp;"in_adj",&nbsp;"cl_adj",&nbsp;"init_adj","pwr_adj",&nbsp;"hp_adj",&nbsp;"luck_adj",&nbsp;"ability_cost" Of course you can also just have " change:repeating_powers &nbsp;remove :repeating_powers " instead. So you need to restructure your code somewhat. There are two main ways to do it. The first way to do it builds faster and more efficient, but is much harder to write and maintain. That is to just have one sheet worker: one giant change event which watches all stats for changes, one getAttrs which grabs all stats, one body which performs all calculations, and one setAttrs that updates all attributes. With this method you cant use sumrepeating - you need to write an embedded section which scans the repeating section, so all the calculations can be done at once. The second method is less efficient - and in some cases can be very &nbsp;inefficient and lead to obvious sheet lag. But your sheet isnt big enough for that to be much of an issue. It is much easier to maintain: you break the code down to smaller modular functions, and let roll20's events keep everything in sync.&nbsp; The main work would be reorganising your main functions into the following three: on('change:repeating_powers&nbsp;remove:repeating_powers',&nbsp;function()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;repeatingSum(["abilityip_total",&nbsp;"st_adj_total",&nbsp;"en_adj_total",&nbsp;"ag_adj_total",&nbsp;"in_adj_total",&nbsp;"cl_adj_total","init_adj_total",&nbsp;"pwr_adj_total",&nbsp;"hp_adj_total",&nbsp;"luck_adj_total",&nbsp;"ability_cp_total"], &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"powers", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;["ip_cost",&nbsp;"st_adj",&nbsp;"en_adj",&nbsp;"ag_adj",&nbsp;"in_adj",&nbsp;"cl_adj",&nbsp;"init_adj","pwr_adj",&nbsp;"hp_adj",&nbsp;"luck_adj",&nbsp;"ability_cost"]); }); const somanystats = [ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"strength_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"endurance_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"agility_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"intelligence_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"cool_score", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"init_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"hp_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"pwr_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"luck_adj_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"weight", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"ability_cp_total", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"strength_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"endurance_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"agility_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"intelligence_cost", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"cool_cost" &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]; const somanyChanges = somanystats.map(stat =&gt; `change:${stat}`).join(' '); on(`${somanyChanges}`, function() { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getAttrs(somanystats,&nbsp;function(v)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;statTable&nbsp;=&nbsp;[ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;0,&nbsp;max:&nbsp;0,&nbsp;carry:&nbsp;8,&nbsp;hth_init:&nbsp;'d2-1',&nbsp;save:&nbsp;6,&nbsp;hits_st:&nbsp;-3,&nbsp;hits_en:&nbsp;-5,&nbsp;hits_ag:&nbsp;-2,&nbsp;hits_cl:&nbsp;-1,&nbsp;heal:&nbsp;.2}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;1,&nbsp;max:&nbsp;1,&nbsp;carry:&nbsp;10,&nbsp;hth_init:&nbsp;'d2-1',&nbsp;save:&nbsp;7,&nbsp;hits_st:&nbsp;-3,&nbsp;hits_en:&nbsp;-5,&nbsp;hits_ag:&nbsp;-2,&nbsp;hits_cl:&nbsp;-1,&nbsp;heal:&nbsp;.3}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;2,&nbsp;max:&nbsp;2,&nbsp;carry:&nbsp;12,&nbsp;hth_init:&nbsp;'d2-1',&nbsp;save:&nbsp;7,&nbsp;hits_st:&nbsp;-3,&nbsp;hits_en:&nbsp;-5,&nbsp;hits_ag:&nbsp;-2,&nbsp;hits_cl:&nbsp;-1,&nbsp;heal:&nbsp;.3}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;3,&nbsp;max:&nbsp;5,&nbsp;carry:&nbsp;15,&nbsp;hth_init:&nbsp;'d2',&nbsp;save:&nbsp;8,&nbsp;hits_st:&nbsp;-2,&nbsp;hits_en:&nbsp;-3,&nbsp;hits_ag:&nbsp;-1,&nbsp;hits_cl:&nbsp;-0,&nbsp;heal:&nbsp;.5}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;6,&nbsp;max:&nbsp;8,&nbsp;carry:&nbsp;30,&nbsp;hth_init:&nbsp;'d3',&nbsp;save:&nbsp;9,&nbsp;&nbsp;hits_st:&nbsp;0,&nbsp;hits_en:&nbsp;-1,&nbsp;hits_ag:&nbsp;0,&nbsp;hits_cl:&nbsp;1,&nbsp;heal:&nbsp;.8}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;9,&nbsp;max:&nbsp;11,&nbsp;carry:&nbsp;60,&nbsp;hth_init:&nbsp;'d4',&nbsp;save:&nbsp;10,&nbsp;hits_st:&nbsp;1,&nbsp;hits_en:&nbsp;1,&nbsp;hits_ag:&nbsp;1,&nbsp;hits_cl:&nbsp;1,&nbsp;heal:&nbsp;1}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;12,&nbsp;max:&nbsp;14,&nbsp;carry:&nbsp;120,&nbsp;hth_init:&nbsp;'d6',&nbsp;save:&nbsp;11,&nbsp;hits_st:&nbsp;3,&nbsp;hits_en:&nbsp;3,&nbsp;hits_ag:&nbsp;2,&nbsp;hits_cl:&nbsp;2,&nbsp;heal:&nbsp;1.6}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;15,&nbsp;max:&nbsp;17,&nbsp;carry:&nbsp;240,&nbsp;hth_init:&nbsp;'d6+1',&nbsp;save:&nbsp;11,&nbsp;hits_st:&nbsp;5,&nbsp;hits_en:&nbsp;6,&nbsp;hits_ag:&nbsp;3,&nbsp;hits_cl:&nbsp;2,&nbsp;heal:&nbsp;2.2}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;18,&nbsp;max:&nbsp;20,&nbsp;carry:&nbsp;480,&nbsp;hth_init:&nbsp;'d8+1',&nbsp;save:&nbsp;12,&nbsp;hits_st:&nbsp;6,&nbsp;hits_en:&nbsp;8,&nbsp;hits_ag:&nbsp;5,&nbsp;hits_cl:&nbsp;3,&nbsp;heal:&nbsp;2.8}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;21,&nbsp;max:&nbsp;23,&nbsp;carry:&nbsp;960,&nbsp;hth_init:&nbsp;'d10+1',&nbsp;save:&nbsp;12,&nbsp;hits_st:&nbsp;8,&nbsp;hits_en:&nbsp;10,&nbsp;hits_ag:&nbsp;6,&nbsp;hits_cl:&nbsp;3,&nbsp;heal:&nbsp;3.4}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;24,&nbsp;max:&nbsp;26,&nbsp;carry:&nbsp;1920,&nbsp;hth_init:&nbsp;'2d6',&nbsp;save:&nbsp;13,&nbsp;hits_st:&nbsp;10,&nbsp;hits_en:&nbsp;13,&nbsp;hits_ag:&nbsp;7,&nbsp;hits_cl:&nbsp;5,&nbsp;heal:&nbsp;3.9}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;27,&nbsp;max:&nbsp;29,&nbsp;carry:&nbsp;3840,&nbsp;hth_init:&nbsp;'d6+d8',&nbsp;save:&nbsp;13,&nbsp;hits_st:&nbsp;12,&nbsp;hits_en:&nbsp;15,&nbsp;hits_ag:&nbsp;8,&nbsp;hits_cl:&nbsp;5,&nbsp;heal:&nbsp;4.5}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;30,&nbsp;max:&nbsp;32,&nbsp;carry:&nbsp;7680,&nbsp;hth_init:&nbsp;'2d8',&nbsp;save:&nbsp;14,&nbsp;hits_st:&nbsp;14,&nbsp;hits_en:&nbsp;17,&nbsp;hits_ag:&nbsp;9,&nbsp;hits_cl:&nbsp;5,&nbsp;heal:&nbsp;5.1}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;33,&nbsp;max:&nbsp;35,&nbsp;carry:&nbsp;15360,&nbsp;hth_init:&nbsp;'d8+d10',&nbsp;save:&nbsp;14,&nbsp;hits_st:&nbsp;16,&nbsp;hits_en:&nbsp;20,&nbsp;hits_ag:&nbsp;10,&nbsp;hits_cl:&nbsp;6,&nbsp;heal:&nbsp;5.7}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;36,&nbsp;max:&nbsp;38,&nbsp;carry:&nbsp;30720,&nbsp;hth_init:&nbsp;'2d10',&nbsp;save:&nbsp;15,&nbsp;hits_st:&nbsp;17,&nbsp;hits_en:&nbsp;22,&nbsp;hits_ag:&nbsp;12,&nbsp;hits_cl:&nbsp;6,&nbsp;heal:&nbsp;6.3}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;39,&nbsp;max:&nbsp;41,&nbsp;carry:&nbsp;61440,&nbsp;hth_init:&nbsp;'d10+d12',&nbsp;save:&nbsp;15,&nbsp;hits_st:&nbsp;19,&nbsp;hits_en:&nbsp;25,&nbsp;hits_ag:&nbsp;13,&nbsp;hits_cl:&nbsp;7,&nbsp;heal:&nbsp;6.9}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;42,&nbsp;max:&nbsp;44,&nbsp;carry:&nbsp;122880,&nbsp;hth_init:&nbsp;'2d12',&nbsp;save:&nbsp;16,&nbsp;hits_st:&nbsp;21,&nbsp;hits_en:&nbsp;27,&nbsp;hits_ag:&nbsp;14,&nbsp;hits_cl:&nbsp;7,&nbsp;heal:&nbsp;7.5}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;45,&nbsp;max:&nbsp;47,&nbsp;carry:&nbsp;245760,&nbsp;hth_init:&nbsp;'3d8',&nbsp;save:&nbsp;16,&nbsp;hits_st:&nbsp;23,&nbsp;hits_en:&nbsp;29,&nbsp;hits_ag:&nbsp;15,&nbsp;hits_cl:&nbsp;8,&nbsp;heal:&nbsp;8.1}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;48,&nbsp;max:&nbsp;50,&nbsp;carry:&nbsp;491520,&nbsp;hth_init:&nbsp;'2d8+d10',&nbsp;save:&nbsp;17,&nbsp;hits_st:&nbsp;25,&nbsp;hits_en:&nbsp;32,&nbsp;hits_ag:&nbsp;16,&nbsp;hits_cl:&nbsp;9,&nbsp;heal:&nbsp;8.7}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;51,&nbsp;max:&nbsp;53,&nbsp;carry:&nbsp;983040,&nbsp;hth_init:&nbsp;'d8+2d10',&nbsp;save:&nbsp;17,&nbsp;hits_st:&nbsp;27,&nbsp;hits_en:&nbsp;34,&nbsp;hits_ag:&nbsp;17,&nbsp;hits_cl:&nbsp;9,&nbsp;heal:&nbsp;9.2}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;54,&nbsp;max:&nbsp;56,&nbsp;carry:&nbsp;1966080,&nbsp;hth_init:&nbsp;'3d10',&nbsp;save:&nbsp;18,&nbsp;hits_st:&nbsp;28,&nbsp;hits_en:&nbsp;36,&nbsp;hits_ag:&nbsp;19,&nbsp;hits_cl:&nbsp;10,&nbsp;heal:&nbsp;9.8}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;57,&nbsp;max:&nbsp;59,&nbsp;carry:&nbsp;3932160,&nbsp;hth_init:&nbsp;'2d10+d12',&nbsp;save:&nbsp;18,&nbsp;hits_st:&nbsp;30,&nbsp;hits_en:&nbsp;39,&nbsp;hits_ag:&nbsp;20,&nbsp;hits_cl:&nbsp;10,&nbsp;heal:&nbsp;10.4}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;60,&nbsp;max:&nbsp;62,&nbsp;carry:&nbsp;7864320,&nbsp;hth_init:&nbsp;'d10+2d12',&nbsp;save:&nbsp;19,&nbsp;hits_st:&nbsp;32,&nbsp;hits_en:&nbsp;41,&nbsp;hits_ag:&nbsp;21,&nbsp;hits_cl:&nbsp;11,&nbsp;heal:&nbsp;11}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;63,&nbsp;max:&nbsp;65,&nbsp;carry:&nbsp;15728640,&nbsp;hth_init:&nbsp;'3d12',&nbsp;save:&nbsp;19,&nbsp;hits_st:&nbsp;34,&nbsp;hits_en:&nbsp;43,&nbsp;hits_ag:&nbsp;22,&nbsp;hits_cl:&nbsp;12,&nbsp;heal:&nbsp;11.6}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;66,&nbsp;max:&nbsp;68,&nbsp;carry:&nbsp;31457280,&nbsp;hth_init:&nbsp;'3d12+1',&nbsp;save:&nbsp;20,&nbsp;hits_st:&nbsp;36,&nbsp;hits_en:&nbsp;46,&nbsp;hits_ag:&nbsp;23,&nbsp;hits_cl:&nbsp;12,&nbsp;heal:&nbsp;12.2}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;69,&nbsp;max:&nbsp;71,&nbsp;carry:&nbsp;62914560,&nbsp;hth_init:&nbsp;'3d12+2',&nbsp;save:&nbsp;20,&nbsp;hits_st:&nbsp;38,&nbsp;hits_en:&nbsp;48,&nbsp;hits_ag:&nbsp;25,&nbsp;hits_cl:&nbsp;13,&nbsp;heal:&nbsp;12.8}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;72,&nbsp;max:&nbsp;74,&nbsp;carry:&nbsp;125829120,&nbsp;hth_init:&nbsp;'4d10',&nbsp;save:&nbsp;21,&nbsp;hits_st:&nbsp;39,&nbsp;hits_en:&nbsp;50,&nbsp;hits_ag:&nbsp;26,&nbsp;hits_cl:&nbsp;13,&nbsp;heal:&nbsp;13.4}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;75,&nbsp;max:&nbsp;77,&nbsp;carry:&nbsp;251658240,&nbsp;hth_init:&nbsp;'3d10+d12',&nbsp;save:&nbsp;21,&nbsp;hits_st:&nbsp;41,&nbsp;hits_en:&nbsp;53,&nbsp;hits_ag:&nbsp;27,&nbsp;hits_cl:&nbsp;14,&nbsp;heal:&nbsp;14}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;78,&nbsp;max:&nbsp;80,&nbsp;carry:&nbsp;503316480,&nbsp;hth_init:&nbsp;'2d10+2d12',&nbsp;save:&nbsp;22,&nbsp;hits_st:&nbsp;43,&nbsp;hits_en:&nbsp;55,&nbsp;hits_ag:&nbsp;28,&nbsp;hits_cl:&nbsp;15,&nbsp;heal:&nbsp;14.5}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;81,&nbsp;max:&nbsp;83,&nbsp;carry:&nbsp;1006632960,&nbsp;hth_init:&nbsp;'d10+3d12',&nbsp;save:&nbsp;22,&nbsp;hits_st:&nbsp;45,&nbsp;hits_en:&nbsp;58,&nbsp;hits_ag:&nbsp;29,&nbsp;hits_cl:&nbsp;15,&nbsp;heal:&nbsp;15.1}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;84,&nbsp;max:&nbsp;86,&nbsp;carry:&nbsp;2013265920,&nbsp;hth_init:&nbsp;'4d12',&nbsp;save:&nbsp;23,&nbsp;hits_st:&nbsp;47,&nbsp;hits_en:&nbsp;60,&nbsp;hits_ag:&nbsp;30,&nbsp;hits_cl:&nbsp;16,&nbsp;heal:&nbsp;15.7}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;87,&nbsp;max:&nbsp;89,&nbsp;carry:&nbsp;4026531840,&nbsp;hth_init:&nbsp;'4d12+1',&nbsp;save:&nbsp;23,&nbsp;hits_st:&nbsp;48,&nbsp;hits_en:&nbsp;62,&nbsp;hits_ag:&nbsp;32,&nbsp;hits_cl:&nbsp;16,&nbsp;heal:&nbsp;16.3}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;90,&nbsp;max:&nbsp;92,&nbsp;carry:&nbsp;8053063680,&nbsp;hth_init:&nbsp;'5d10',&nbsp;save:&nbsp;24,&nbsp;hits_st:&nbsp;50,&nbsp;hits_en:&nbsp;65,&nbsp;hits_ag:&nbsp;33,&nbsp;hits_cl:&nbsp;17,&nbsp;heal:&nbsp;16.9}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;93,&nbsp;max:&nbsp;95,&nbsp;carry:&nbsp;16106127360,&nbsp;hth_init:&nbsp;'4d10+d12',&nbsp;save:&nbsp;24,&nbsp;hits_st:&nbsp;52,&nbsp;hits_en:&nbsp;67,&nbsp;hits_ag:&nbsp;34,&nbsp;hits_cl:&nbsp;17,&nbsp;heal:&nbsp;17.5}, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{min:&nbsp;96,&nbsp;max:&nbsp;98,&nbsp;carry:&nbsp;32212254720,&nbsp;hth_init:&nbsp;'3d10+2d12',&nbsp;save:&nbsp;25,&nbsp;hits_st:&nbsp;54,&nbsp;hits_en:&nbsp;69,&nbsp;hits_ag:&nbsp;35,&nbsp;hits_cl:&nbsp;18,&nbsp;heal:&nbsp;18.1} &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;let&nbsp;stResult&nbsp;=&nbsp;statTable.filter(tableRow&nbsp;=&gt;&nbsp;(tableRow.min&nbsp;&lt;=&nbsp;v.strength_score&nbsp;&amp;&amp;&nbsp;tableRow.max&nbsp;&gt;=&nbsp;v.strength_score)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;enResult&nbsp;=&nbsp;statTable.filter(tableRow&nbsp;=&gt;&nbsp;(tableRow.min&nbsp;&lt;=&nbsp;v.endurance_score&nbsp;&amp;&amp;&nbsp;tableRow.max&nbsp;&gt;=&nbsp;v.endurance_score)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;agResult&nbsp;=&nbsp;statTable.filter(tableRow&nbsp;=&gt;&nbsp;(tableRow.min&nbsp;&lt;=&nbsp;v.agility_score&nbsp;&amp;&amp;&nbsp;tableRow.max&nbsp;&gt;=&nbsp;v.agility_score)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;inResult&nbsp;=&nbsp;statTable.filter(tableRow&nbsp;=&gt;&nbsp;(tableRow.min&nbsp;&lt;=&nbsp;v.intelligence_score&nbsp;&amp;&amp;&nbsp;tableRow.max&nbsp;&gt;=&nbsp;v.intelligence_score)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;clResult&nbsp;=&nbsp;statTable.filter(tableRow&nbsp;=&gt;&nbsp;(tableRow.min&nbsp;&lt;=&nbsp;v.cool_score&nbsp;&amp;&amp;&nbsp;tableRow.max&nbsp;&gt;=&nbsp;v.cool_score)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;leap&nbsp;=&nbsp;(parseInt(v.weight,10)&nbsp;&gt;&nbsp;0)&nbsp;?&nbsp;Math.floor(stResult[0].carry&nbsp;/&nbsp;parseInt(v.weight,10))&nbsp;:&nbsp;''; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(v.ability_cp_total); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log(v.luck_adj_total); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAttrs({ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;carry_capacity:&nbsp;stResult[0].carry, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;endurance_save:&nbsp;enResult[0].save, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;agility_save:&nbsp;agResult[0].save, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;intelligence_save:&nbsp;inResult[0].save, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cool_save:&nbsp;clResult[0].save, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hth_damage:&nbsp;stResult[0].hth_init, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;initiative_score:&nbsp;simplifyDice(clResult[0].hth_init&nbsp;+&nbsp;'+'&nbsp;+&nbsp;v.init_adj_total), &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;luck:&nbsp;10+(parseInt(v.luck_adj_total,10)&nbsp;||&nbsp;0), &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;power_score_max:&nbsp;(parseInt(v.strength_score,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.endurance_score,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.agility_score,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.intelligence_score,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.pwr_adj_total,10)&nbsp;||&nbsp;0), &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hits_score_max:&nbsp;stResult[0].hits_st&nbsp;+&nbsp;enResult[0].hits_en&nbsp;+&nbsp;agResult[0].hits_ag&nbsp;+&nbsp;clResult[0].hits_cl&nbsp;+&nbsp;(parseInt(v.hp_adj_total,10)&nbsp;||&nbsp;0), &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;movement_leap:&nbsp;leap, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;healing_rate:&nbsp;enResult[0].heal, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;power_level:&nbsp;(parseInt(v.ability_cp_total,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.strength_cost,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.agility_cost,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.endurance_cost,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.intelligence_cost,10)&nbsp;||&nbsp;0)&nbsp;+&nbsp;(parseInt(v.cool_cost,10)&nbsp;||&nbsp;0) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}; ["strength",&nbsp;&nbsp;"endurance",&nbsp;&nbsp;&nbsp;"agility",&nbsp;&nbsp;"intelligence",&nbsp;&nbsp;"cool"].forEach(stat&nbsp;=&gt;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;stat_cost&nbsp;=&nbsp;`${stat}_cost`,&nbsp;stat_adj&nbsp;=&nbsp;(stat&nbsp;==&nbsp;'cool')&nbsp;?&nbsp;`cl_adj_total`&nbsp;:&nbsp;`${stat.slice(0,2)}_adj_total`; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;on(`change:${stat_cost}&nbsp;change:${stat_adj}`,&nbsp;function()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;getAttrs([stat_cost,&nbsp;stat_adj],&nbsp;function(v)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;cost&nbsp;=&nbsp;+v[stat_cost]&nbsp;||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;adj&nbsp;=&nbsp;&nbsp;+v[stat_adj]&nbsp;||0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;score&nbsp;=&nbsp;cost&nbsp;+&nbsp;adj; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAttrs({ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[`${stat}_score`]:&nbsp;score &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;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}); Notice that each function includes only one getAttrs and setAttrs, and doesnt call any other processes that contain those functions. This allows roll20's events (the change:stat &nbsp;lines) to keep everything synchronised. When one worker runs, and runs setAttrs, the sheet gets updated. When the sheet gets updated, any new events trigger, and they update the sheet again. That triggers new events. And this process continues until the sheet is fully updated.&nbsp; The longer that chain takes the more inefficient the sheet is - but in your sheet that should never take make than 2 or 3 passes.&nbsp; The thing that keeps everything in sync: the sheet workers are always using the most up-to-date versions of the attributes. A setAttrs runs, triggering a new getAttrs - but that getAttrs has the values of the attributes that just changed. Your current sheet doesnt run like that, and thats where the error comes in. I threw the functions above together very quickly,making the smallest changes I could to the existing functions. You'll need to check they work properly. And make sure to remove the functions they replace.