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

on change bug

I have this sheet worker, which serves to populate  repeating_skills_skill_ability_bonus_clean  .  Here it is: // update skill ability bonus on('change:repeating_skills:skill_ability_group change:repeating_skills:skill_ability_adjusted change:physical_strength_skill_adjustment_clean change:physical_endurance_skill_adjustment_clean change:agility_skill_adjustment_clean change:observation_skill_adjustment_clean change:mental_strength_skill_adjustment_clean change:mental_endurance_skill_adjustment_clean change:personality_skill_adjustment_clean change:mystic_talent_skill_adjustment_clean change:mystic_power_skill_adjustment_clean', () => { getAttrs(['repeating_skills_skill_ability_group', 'repeating_skills_skill_ability_adjusted', 'physical_strength_skill_adjustment_clean', 'physical_endurance_skill_adjustment_clean', 'agility_skill_adjustment_clean', 'observation_skill_adjustment_clean', 'mental_strength_skill_adjustment_clean', 'mental_endurance_skill_adjustment_clean', 'personality_skill_adjustment_clean', 'mystic_talent_skill_adjustment_clean', 'mystic_power_skill_adjustment_clean'], values => { const skl_abl_grp = int(values.repeating_skills_skill_ability_group); const skl_abl_adj = int(values.repeating_skills_skill_ability_adjusted); const phy_str_skl_adj = int(values.physical_strength_skill_adjustment_clean); const phy_end_skl_adj = int(values.physical_endurance_skill_adjustment_clean); const agi_skl_adj = int(values.agility_skill_adjustment_clean); const obs_skl_adj = int(values.observation_skill_adjustment_clean); const mnt_str_skl_adj = int(values.mental_strength_skill_adjustment_clean); const mnt_end_skl_adj = int(values.mental_endurance_skill_adjustment_clean); const per_skl_adj = int(values.personality_skill_adjustment_clean); const mys_tal_skl_adj = int(values.mystic_talent_skill_adjustment_clean); const mys_pow_skl_adj = int(values.mystic_power_skill_adjustment_clean); const skill_ability_bonus_table = [ /* 0 */ 0, /* 1 */ phy_str_skl_adj, /* 2 */ phy_end_skl_adj, /* 3 */ agi_skl_adj, /* 4 */ obs_skl_adj, /* 5 */ mnt_str_skl_adj, /* 6 */ mnt_end_skl_adj, /* 7 */ per_skl_adj, /* 8 */ mys_tal_skl_adj, /* 9 */ mys_pow_skl_adj ]; var skl_abl_bon_clean = 0; if (skl_abl_adj > 0) skl_abl_bon_clean = skill_ability_bonus_table[skl_abl_grp]; var plus = ""; if (skl_abl_bon_clean > -1) plus = "+"; const skl_abl_bon = plus + skl_abl_bon_clean; console.log( 'update skill ability bonus' + '\n', 'repeating_skills_skill_ability_bonus_clean: ' + skl_abl_bon_clean + '\n', 'repeating_skills_skill_ability_bonus: ' + skl_abl_bon ); setAttrs({ repeating_skills_skill_ability_bonus_clean: skl_abl_bon_clean, repeating_skills_skill_ability_bonus: skl_abl_bon }); }); }); The problem is that when the included change conditions cause it to run, it instead throws errors in the console output.  These are those errors: SHEET WORKER ERROR: You attempted to set an attribute beginning with 'repeating_' but did not include a Row ID or Attribute Name in repeating_skillsepeating_skills_skill_ability_bonus_clean app.js?1598975515:577 SHEET WORKER ERROR: You attempted to set an attribute beginning with 'repeating_' but did not include a Row ID or Attribute Name in repeating_skillsepeating_skills_skill_ability_bonus These errors specify these values: repeating_skillsepeating_skills_skill_ability_bonus_clean repeating_skillsepeating_skills_skill_ability_bonus However those values don't exist.  Instead they are corrupted versions of these values: repeating_skills_skill_ability_bonus_clean repeating_skills_skill_ability_bonus Here are some of the values the sheet worker uses: <div class="showSkills"> <div class="veryLightBlueBackground tabBorder skillsScrollbar baseScrollbar"> <div class="blueGradientBackground sectionLabelCenter white sticky repeatingLabel">Skills</div> <fieldset class="repeating_skills"> <div class="expandable-section"> <input class="expand-control" type="hidden" name="attr_skill_label_checkbox_expand" value="1"/> <div class="collapsed-view"> <!--What you want shown when the section is collapsed goes here--> </div> <div class="expanded-view"> <input class="field blueGradientBackground white manual19" name="attr_skill_label_name" type="text"> </div> </div> <div class="expandable-section paleGreenGradient"> <input class="expand-control" type="hidden" name="attr_skill_checkbox_expand" value="1"/> <div class="skillsTitleGrid"> <div class="sklabg blueBackground section"> <div class="fieldLabelCenter white section">Ability Group</div> <select name="attr_skill_ability_group" class="dropDown manual20"> <option value="0"></option> <option value="1">Physical Strength</option> <option value="2">Physical Endurance</option> <option value="3">Agility</option> <option value="4">Observation</option> <option value="5">Mental Strength</option> <option value="6">Mental Endurance</option> <option value="7">Personality</option> <option value="8">Mystic Talent</option> <option value="9">Mystic Power</option> </select> </div> </div> <div class="collapsed-view"> <!--What you want shown when the section is collapsed goes here--> </div> <div class="expanded-view"> <div class="skillsBodyGrid"> <div class="sklaaj section"> <div class="fieldLabelCenter white blueBackground section">Ability Adjusted?</div> <select name="attr_skill_ability_adjusted" class="dropDown transparentBackground manual20"> <option value="0">No</option> <option value="1">Yes</option> </select> </div> <div class="sklabb section"> <div class="fieldLabelCenter white blueBackground section">Ability Bonus</div> <input name="attr_skill_ability_bonus_clean" type="hidden" value="0"> <input name="attr_skill_ability_bonus" type="hidden" value="0"> <span class="field transparentBackground bottomBorder width76 derived22" name="attr_skill_ability_bonus"></span> </div> </div> </div> </div> </fieldset> </div> </div> Here are the other values that the sheet worker uses: <div class="showAttributes"> <div class="attributesGrid blueGradientBackground tabBorder"> <div class="psskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_physical_strength_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_physical_strength_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_physical_strength_skill_adjustment"></span> </div> <div class="peskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_physical_endurance_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_physical_endurance_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_physical_endurance_skill_adjustment"></span> </div> <div class="agskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_agility_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_agility_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_agility_skill_adjustment"></span> </div> <div class="obskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_observation_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_observation_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_observation_skill_adjustment"></span> </div> <div class="msskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_mental_strength_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_mental_strength_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_mental_strength_skill_adjustment"></span> </div> <div class="meskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_mental_endurance_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_mental_endurance_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_mental_endurance_skill_adjustment"></span> </div> <div class="prskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_personality_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_personality_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_personality_skill_adjustment"></span> </div> <div class="mtskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_mystic_talent_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_mystic_talent_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_mystic_talent_skill_adjustment"></span> </div> <div class="mpskaj blueBackground blueBorderTop1 section"> <div class="fieldLabelCenter white section">Skill Adj</div> <input name="attr_mystic_power_skill_adjustment_clean" type="hidden" value="0"> <input name="attr_mystic_power_skill_adjustment" type="hidden" value="0"> <span class="field derived21" name="attr_mystic_power_skill_adjustment"></span> </div>     </div> </div> Let's assume that  repeating_skills_skill_ability_group  and  repeating_skills_skill_ability_adjusted  are both set to 1.  Under these conditions,  repeating_skills_skill_ability_bonus_clean   is supposed to change based on changes to  physical_strength_skill_adjustment_clean  .  For the purposes of this issue, it doesn't matter whether it's  physical_strength_skill_adjustment_clean   or another value.  They are all changed using the same methods.  Here is the worker that sets  physical_strength_skill_adjustment_clean  : // set physical strength fields         on('change:physical_strength change:physicalstrength_modifier_total', () => {     getAttrs(['physical_strength', 'physicalstrength_modifier_total'], values => {         const strength_temp = int(values.physicalstrength_modifier_total); const strength_base = int(values.physical_strength); const strength_total = strength_base + strength_temp; const derived_physical_strength_table = [     /* physical_strength_total     [melee_damage_adjustment,     maximum_encumbrance,              maximum_weight,         physical_strength_skill_adjustment,         physical_strength_skill_limit] */     /* 1-5 */     [-4,    2,      30, -30, 1],     /* 6-10 */     [-3,    5,      40, -30, 2],     /* 11-15 */     [-2,    10,      50, -25, 3],     /* 16-20 */     [-1,    20,      60, -20, 4],     /* 21-25 */     [-1,    30,      70, -15, 5],     /* 26-30 */     [-1,    35,      80, -10, 5],     /* 31-35 */     [-1,    35,      80, -10, 5],     /* 36-40 */     [-1,    40,      90, -5, 6],     /* 41-45 */     [0,     45,      115, 0, 6],     /* 46-50 */     [0,     60,      140, 0, 7],     /* 51-55 */     [0,     70,      170, 0, 7],     /* 56-60 */     [1,     95,      220, 0, 8],     /* 61-65 */     [1,     120,     255, 0, 8],     /* 66-70 */     [2,     130,     280, 0, 9],     /* 71-75 */     [3,     150,     305, 5, 9],     /* 76-80 */     [3,     165,     330, 10, 10],     /* 81-85 */     [3,     165,     330, 10, 10],     /* 86-90 */     [4,     180,     380, 15, 10],     /* 91-95 */     [5,     200,     480, 20, 11],     /* 96-100 */    [6,     300,     640, 25, 11],     /* 101-110 */   [7,     350,     700, 30, 12],     /* 111-120 */   [8,     390,     810, 35, 12],     /* 121-130 */   [9,     450,     970, 40, 13],     /* 131-140 */   [10,    550,     1130, 45, 13],     /* 141-150 */   [11,    625,     1440, 50, 14],     /* 151-160 */   [12,    800,     1750, 55, 14],     /* 161-170 */   [14,    950,     2000, 60, 15],     /* 171-180 */   [15,    1050,    2250, 60, 15],     /* 181-190 */   [16,    1100,    2500, 60, 15],     /* 191-200 */   [17,    1250,    2750, 60, 15],     /* 201-210 */   [18,    1600,    3000, 60, 15],     /* 211-220 */   [19,    1750,    3250, 60, 15] ]; const length = derived_physical_strength_table.length; var lookup = (strength_total < 100) ? Math.ceil(strength_total/5) : Math.ceil(strength_total/10) +10; if (lookup < 1) lookup = 1; if (lookup > length) lookup = length; const lookup_value = lookup - 1; const melee_damage_adj_clean = derived_physical_strength_table[lookup_value][0]; var plus = ""; if (melee_damage_adj_clean > -1) plus = "+"; const melee_damage_adj = plus + melee_damage_adj_clean; const max_encumbrance_clean = derived_physical_strength_table[lookup_value][1]; const max_encumbrance = max_encumbrance_clean + " pounds"; const max_weight_clean = derived_physical_strength_table[lookup_value][2]; const max_weight = max_weight_clean + " pounds"; const phy_str_skl_adj_clean = derived_physical_strength_table[lookup_value][3]; var plus = ""; if (phy_str_skl_adj_clean > -1) plus = "+"; const phy_str_skl_adj = plus + phy_str_skl_adj_clean; const phy_str_skl_limit_clean = derived_physical_strength_table[lookup_value][4]; var plural = ""; if (phy_str_skl_limit_clean > 1) plural = "s"; const phy_str_skl_limit = phy_str_skl_limit_clean + " skill" + plural; console.log( 'set physical strength fields' + '\n', 'physical_strength_total: ' + strength_total + '\n', 'lookup_value: ' + lookup_value + '\n', 'melee_damage_adjustment_clean: ' + melee_damage_adj_clean + '\n', 'melee_damage_adjustment: ' + melee_damage_adj + '\n', 'maximum_encumbrance_clean: ' + max_encumbrance_clean + '\n', 'maximum_encumbrance: ' + max_encumbrance + '\n', 'maximum_weight_clean: ' + max_weight_clean + '\n', 'maximum_weight: ' + max_weight + '\n', 'physical_strength_skill_adjustment_clean: ' + phy_str_skl_adj_clean + '\n', 'physical_strength_skill_adjustment: ' + phy_str_skl_adj + '\n', 'physical_strength_skill_limit_clean: ' + phy_str_skl_limit_clean + '\n', 'physical_strength_skill_limit: ' + phy_str_skl_limit ); setAttrs({ physical_strength_total: strength_total, melee_damage_adjustment_clean: melee_damage_adj_clean, melee_damage_adjustment: melee_damage_adj, maximum_encumbrance_clean: max_encumbrance_clean, maximum_encumbrance: max_encumbrance, maximum_weight_clean: max_weight_clean, maximum_weight: max_weight, physical_strength_skill_adjustment_clean: phy_str_skl_adj_clean, physical_strength_skill_adjustment: phy_str_skl_adj, physical_strength_skill_limit_clean: phy_str_skl_limit_clean, physical_strength_skill_limit: phy_str_skl_limit }); }); }); Anyway, I think this is caused by a simple oversight on my part, but it could be more complicated.  I hope someone else sees the cause.  Thanks for taking the time to read this post.  I'm afraid as my sheet gets more mature, it also gets more extensive.
1599023184
GiGs
Pro
Sheet Author
API Scripter
I havent read through all of the code, just the first sheet worker, so there may be other issues. But you do have one critical issue: you have change events triggered on attributes outside a repeating section, that cause changes to attributes inside a repeating section. There is no way to do this without using the getSectionIDs function. Here's the problem: when you use this syntax, repeating_skills_skill_ability_bonus_clean you must  supply a row id. The full name of a repeating attribute is something like: repeating_skills_-GH67947YV8THJW_skill_ability_bonus_clean Now, when you have a change event occurring within a repeating section row , and that change affects only other attributes within that row, or stats outside the section, roll20 supplies the row id automatically. But when you have a change triggered outside the repeating section, roll20 has no idea which row to apply the change to. It's likely you want to apply the change to all of the rows. So you need to use getSectionIDs to get the row ids, so you can construct the proper attribute names. It looks like you need to do something for every row in the repeating section. The proper structure of this kind of sheet worker is: on('change: list of attributes that trigger this worker', () => {     getSectionIDs('repeating_section_name', idarray => {         // create an array to hold all the attribute names needed from the repeating section         const sectionnames = [];         idarray.forEach(id => {             //add each attribute name you need in the section to fieldnames in a form; you can add multiple attributes separated by commas             sectionnames.push(`repeating_section_${id}_attribute_name`);         });         getAttrs(['each', 'attribute', 'outside the section', ...sectionnames], values => {             // initialise an attribute to hold the final attribute list             const output = {}             let bla = 'define each non-repeating section attribute with their values';             idarray.forEach(id => {                 // now loop through the ids, and access the full attribute names for the section                 // and do what you need to get their values                 output['new value'] = 'and set the value you want to save into the output variable like this';             };             setAttrs(output);         });     }); });
1599024485
GiGs
Pro
Sheet Author
API Scripter
As a rough pass, here's what that sheet worker should look like: on('change:repeating_skills:skill_ability_group change:repeating_skills:skill_ability_adjusted ' +     'change:physical_strength_skill_adjustment_clean change:physical_endurance_skill_adjustment_clean change:agility_skill_adjustment_clean change:observation_skill_adjustment_clean change:mental_strength_skill_adjustment_clean change:mental_endurance_skill_adjustment_clean change:personality_skill_adjustment_clean change:mystic_talent_skill_adjustment_clean change:mystic_power_skill_adjustment_clean', () => {     getSectionIDs('repeating_skills', ids => {         const sectionNames = [];         ids.forEach(id => sectionNames.push(             `repeating_skills_${id}_skill_ability_group`,             `repeating_skills_${id}_skill_ability_adjusted`         ));         getAttrs([...sectionNames, 'physical_strength_skill_adjustment_clean', 'physical_endurance_skill_adjustment_clean', 'agility_skill_adjustment_clean', 'observation_skill_adjustment_clean', 'mental_strength_skill_adjustment_clean', 'mental_endurance_skill_adjustment_clean', 'personality_skill_adjustment_clean', 'mystic_talent_skill_adjustment_clean', 'mystic_power_skill_adjustment_clean'], values => {             const phy_str_skl_adj = int(values.physical_strength_skill_adjustment_clean);             const phy_end_skl_adj = int(values.physical_endurance_skill_adjustment_clean);             const agi_skl_adj =     int(values.agility_skill_adjustment_clean);             const obs_skl_adj =     int(values.observation_skill_adjustment_clean);             const mnt_str_skl_adj = int(values.mental_strength_skill_adjustment_clean);             const mnt_end_skl_adj = int(values.mental_endurance_skill_adjustment_clean);             const per_skl_adj =     int(values.personality_skill_adjustment_clean);             const mys_tal_skl_adj = int(values.mystic_talent_skill_adjustment_clean);             const mys_pow_skl_adj = int(values.mystic_power_skill_adjustment_clean);             const skill_ability_bonus_table = [             /* 0 */ 0,                 /* 1 */ phy_str_skl_adj,                 /* 2 */ phy_end_skl_adj,                 /* 3 */ agi_skl_adj,                 /* 4 */ obs_skl_adj,                 /* 5 */ mnt_str_skl_adj,                 /* 6 */ mnt_end_skl_adj,                 /* 7 */ per_skl_adj,                 /* 8 */ mys_tal_skl_adj,                 /* 9 */ mys_pow_skl_adj             ];             const output = {};             ids.forEach(id => {                 const skl_abl_grp = int(values[`repeating_skills_${id}_skill_ability_group`]);                 const skl_abl_adj = int(values[`repeating_skills_${id}_skill_ability_adjusted`]);                 const skl_abl_bon_clean = skl_abl_adj > 0 ? skill_ability_bonus_table[skl_abl_grp] : 0;                 const plus = (skl_abl_bon_clean > -1) ? "+" : "";                 const skl_abl_bon = plus + skl_abl_bon_clean;                 console.log(                     'update skill ability bonus'                        + '\n',                     `repeating_skills_${id}_skill_ability_bonus_clean: ${skl_abl_bon_clean}`    +  '\n',                     `repeating_skills_${id}_skill_ability_bonus: ${skl_abl_bon}`                 );                 output[`repeating_skills_${id}_skill_ability_bonus_clean`] =    skl_abl_bon_clean;                 output[`repeating_skills_${id}_skill_ability_bonus`] =        skl_abl_bon;             });             setAttrs(output);         });     }); });
Thanks again GiGs.  It looks like that fixed the issue.  I may have some follow up questions on the new syntax, namely: const skl_abl_bon_clean = skl_abl_adj > 0 ? skill_ability_bonus_table[skl_abl_grp] : 0; and console.log( 'update skill ability bonus'          + '\n', `repeating_skills_${id}_skill_ability_bonus_clean: ${skl_abl_bon_clean}` + '\n', `repeating_skills_${id}_skill_ability_bonus: ${skl_abl_bon}` ); output[`repeating_skills_${id}_skill_ability_bonus_clean`] = skl_abl_bon_clean; output[`repeating_skills_${id}_skill_ability_bonus`] = skl_abl_bon; but, I think I can probably deduce it.
1599106160
GiGs
Pro
Sheet Author
API Scripter
This is a ternary operator: const skl_abl_bon_clean = skl_abl_adj > 0 ? skill_ability_bonus_table[skl_abl_grp] : 0; When you have an if statement to select between 2 values, it is a much better way to do it. It's format is variable to assign = (logic test) ? (value if true) : (value if false). So the logic test is your if statement, which evaluates to true or false (or more accurately in javascript, a truthy or falsy value).  Based on that, the variable is assigned either the value before the colon, or the value after the colon. This part: output[`repeating_skills_${id}_skill_ability_bonus_clean`] = skl_abl_bon_clean; In javascript there's a variable type called a Javascript Object, usually referred to as just an object. You have used these already - when you use getAttrs, it creates a javascript object often called values. Likewise when you use setAttrs, you are passing a javascript object. When you do this: setAttrs( {     key: value,     key2: value2 } ); the bit in the curly brackets is a javascript object: {     key: value,     key2: value2 } An object can hold multiple variables with in it. The above example has a variable named key, and another named key2. When you do this: getAttrs(['str', 'dex'], values => {     let dex = values.dex;     let str = values.str; }); getAttrs is looking at the character sheet, finding the dex and str attributes, and storing them in a javascript object called values. Internally that would look like: {     str: 10,     dex: 17 } This part: let str = values.str; is retrieving the str value from the values object, and assigning it to a variable named str for convenient use. You can create your own javascript objects, as I did above: const output = {}; you can then add variables to it, like output.str = str; that would be the above operation in reverse: adding the str value and variable to the output object. It would now look like { str: 10 } Since you spmetimes dont know the actual name of the variable, and have to build it dynamically, there's a second syntax you can use: output['str'] = str; The bit inside the square brackets is a string, and you can build it dynamically. Which is what I did here: output[`repeating_skills_${id}_skill_ability_bonus_clean`] = skl_abl_bon_clean; This part is a special string, called a template literal: `repeating_skills_${id}_skill_ability_bonus_clean` It is the same as doing this: "repeating_skills_" + id + "_skill_ability_bonus_clean" You can use either method - the template literal version looks clunkier until you are used to it, but it is more elegant and much more powerful once you are comfortable with it. Finally, remember setAttrs: setAttrs({     str: 10,     dex: 17 }); since the curly bracket part is just a javascript object, you can pass an object variable directly. Thats what happens here: setAttrs(output); Hope that explanation helps you!
Yes thank you.  That helps very much, especially when you include the terminology as you have. = )
1599107361
GiGs
Pro
Sheet Author
API Scripter
I'm glad to hear it :)
1599322556
vÍnce
Pro
Sheet Author
Tim Z said: Yes thank you.  That helps very much, especially when you include the terminology as you have. = ) 100% agree. GiGs rocks!
1599369459
GiGs
Pro
Sheet Author
API Scripter
Go ahead, make me blush!
1600892567

Edited 1600892589
Wooohoo!  I was able to write this into a new worker without help = D!