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

Sanity check for repeating sheetworker getting multiple values from repeating section.

1700863285

Edited 1700863709
Hopefully my last post for a while, but wanting to be cheeky and check that i've applied my lessons correctly from previous responses in this forum before I go all in on refactoring all my (applicable) sheetworkers to operate in this way. Context around the example, the speed of weapon attacks is dependant on the speed of the user & the speed modifier of the weapon. Inside the repeating section "repeating_attacks" both the attackspeedmod and attackspeed are recorded for use by attack rolls. In the below example both the old and new versions of this sheetworker worked , as in they both output the desired result. The old version had multiple problems though, getAttrs/setAttrs inside for loops etc. The new version includes as many lessons learned as I can muster so as to avoid the many foibles and pitfalls of asynchronous functions. New (hopefully all cleaned up):   //When shell_speed changes, attackspeedmod changes or attackname changes update calculated attack speeds within repeating section.   on ( "change:shell_speed change:repeating_attacks:attackspeedmod change:repeating_attacks:attackname" , function () {     getSectionIDs ( "repeating_attacks" , function ( ids ) {       //init array to hold attribute list with default value of needed global attribute.       let attributes = [ "shell_speed" ];       //for each row, get the rowid to retrieve relevant repeating attributes and push attribute names to an array.       ids . forEach ( rowid => {         let speedmodattr = `repeating_attacks_ ${ rowid } _attackspeedmod` ;         let attackspeedattr = `repeating_attacks_ ${ rowid } _attackspeed` ;         attributes . push ( speedmodattr , attackspeedattr );       })       //get all requested attributes.       getAttrs ( attributes , function ( values ) {         const shellspeed = parseInt ( values . shell_speed )|| 3 ;         //group results by rowid & build final object.         setObj = ids . reduce (( results , rowid ) => {           let speedmodattr = `repeating_attacks_ ${ rowid } _attackspeedmod` ;           let speedmod_value = parseInt ( values [ speedmodattr ])|| 0 ;           let attackspeedattr = `repeating_attacks_ ${ rowid } _attackspeed` ;           let attackspeed_value = shellspeed + speedmod_value ;           if ( attackspeed_value < 1 ) {             attackspeed_value = 1 ;           } else if ( attackspeed_value > 5 ) {             attackspeed_value = 5 ;           }           results [ attackspeedattr ] = attackspeed_value           return results ;         }, {});         setAttrs ( setObj );       });     });   });
1700865810

Edited 1700865903
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
It looks good at a read through. There is one thing I would note though: You probably want this as two separate listeners so that editing the info for one attack doesn't cause your sheetworker to recalculate the info for all attacks. I'd recommend making your calculation a separate function and then call it from your two listeners: /** * Calculates the speed value for a given repeating weapon. * @param {object} values - The values from getAttrs * @param {string} rowid - the Row id of the repeating item * @returns {number} */ const calcSpeedValue = (values,rowid) => { let speedmodattr = `repeating_attacks_${rowid}_attackspeedmod`; let speedmod_value = parseInt(values[speedmodattr])||0; let attackspeed_value = shellspeed + speedmod_value; if (attackspeed_value < 1) { attackspeed_value = 1; } else if (attackspeed_value > 5) { attackspeed_value = 5; } return attackspeed_value; } //When shell_speed changes, attackspeedmod changes or attackname changes update calculated attack speeds within repeating section. on('change:repeating_attacks:attackspeedmod change:repeating_attacks:attackname',(event) => { getAttrs(['shell_speed',event.sourceAttribute],values => { // assemble the attack speed attribute name from the triggering attribute name. const attackspeedattr = event.sourceAttribute.replace(/_[^_]+$/,'_attackspeed'); // extract the rowid from the triggering attribute name const [,rowid] = event.sourceAttribute.match(/repeating_attacks_(.+?)_/) || []; const setObj = { [attackspeedattr]: calcSpeedValue(values,rowid) }; setAttrs(setObj); }) }) on("change:shell_speed", function() { getSectionIDs("repeating_attacks", function (ids) { //init array to hold attribute list with default value of needed global attribute. let attributes = ["shell_speed"]; //for each row, get the rowid to retrieve relevant repeating attributes and push attribute names to an array. ids.forEach(rowid => { let speedmodattr = `repeating_attacks_${rowid}_attackspeedmod`; let attackspeedattr = `repeating_attacks_${rowid}_attackspeed`; attributes.push(speedmodattr,attackspeedattr); }) //get all requested attributes. getAttrs(attributes, function(values) { const shellspeed = parseInt(values.shell_speed)||3; //group results by rowid & build final object. setObj = ids.reduce((results, rowid) => { let attackspeedattr = `repeating_attacks_${rowid}_attackspeed`; results[attackspeedattr] = calcSpeedValue(values,rowid); return results; }, {}); setAttrs(setObj); }); }); });
Thanks Scott,  I may be misinterpretting but i'm not sure your first listener works in all cases. The value of repeating_attacks_-abc123_attackspeedmod is always needed for the attackspeed calculation but "event.sourceAttribute" would only have a valid value to provide to the function if the trigger was itself attackspeedmod. But I can see what you're driving at from a best practice perspective. Academically, seeing as there's one getAttrs & one setAttrs regardless is there likely to be much of a performance hit on updating all values in every case or only if there are 5/10/20+ attacks to update (in this system unlikely)?
1700867382
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Ah, that's what I get for coding quickly :). Yes, you'd want to edit that so you got the correct attributes in both cases. This honestly probably isn't a huge deal, especially in this situation where the thing being calculated is never entered by the user. Unless someone makes several hundred attack entries, there won't be any noticeable performance difference between your original function and the my edit.