Help with ChatSetAttr

What I Already Have I'm using  ChatSetAttr to track training progress. Basically, a player can train in a thing, and after 250 days worth of progress, they can do the thing. But there's a twist, it's not just 1 day = 1 day of progress , but instead the amount of progress is based on a d20 roll each day, plus a relevant attribute modifier (if one applies). Roll 0-10 and a day's training counts as one day Roll 11-20 and a day's training counts as two days Roll 20+ and a day's training counts as three days Modeled thusly: [[ceil((1d20+[[@{attribute_mod}]])/10)]] So I can use ChatSetAttr in a macro to do just this like so: !modattr --sel --training_progress#[[ceil((1d20+[[ ?{Apply Attribute Bonus to Training?|None, 0|Strength, @{strength_mod}|Dexterity, @{dexterity_mod}|Constitution, @{constitution_mod}|Intelligence, @{intelligence_mod}|Wisdom, @{wisdom_mod}|Charisma, @{charisma_mod}} ]])/10)]] What I'd Like to Do 1. See the dice rolls/calculations Right now, the output to chat shows that the script is being called successfully, and shows the new total for the attribute. But I'd like to also see the 1d20 roll in there. 2. Have a Roll Query for Number of Days When a character has a lot of downtime, I'd like to just be able to get the results for x number of days, rather than click the macro button x times. Can either of these be done via macro? Thanks! Karl
1497549080

Edited 1497640244
Silvyre
Roll20 Mod Team
Not possible, unfortunately. 3D Dice might show the d20 roll results. Try this macro out: [[ { ?{Days|1}d20 + ?{Apply Attribute Bonus to Training?|None, 0|Strength, @{strength_mod}|Dexterity, @{dexterity_mod}|Constitution, @{constitution_mod}|Intelligence, @{intelligence_mod}|Wisdom, @{wisdom_mod}|Charisma, @{charisma_mod}} } >21f<10 + 2 * ?{Days} ]] The macro uses a Grouped Roll (bolded).
I modified my macro with your grouped roll suggestion: !modattr --sel --training_progress#[[ { ceil(( ?{Days|1} d20+[[ ?{Apply Attribute Bonus to Training?|None, 0|Strength, @{strength_mod}|Dexterity, @{dexterity_mod}|Constitution, @{constitution_mod}|Intelligence, @{intelligence_mod}|Wisdom, @{wisdom_mod}|Charisma, @{charisma_mod}} ]])/10) } ]] And it appears to do what I need with regard to #2, rolling for multiple days at once. But even with the 3D dice, I still can't see the rolls/calculations, so I can only assume it's getting correct values. But thanks for getting me 1 step closer!
1497619678

Edited 1497619739
Jakob
Pro
Sheet Author
API Scripter
Okay, this is quickly copy&pasted together from one of my other scripts, but this should be a roll listener that listens to any roll starting with !modattr --sel --training_progress and gives you the dice results. on('chat:message', msg => {   let makeInlineroll = function(roll) {       let boundary = '';       switch (detectCritical(roll.results)) {         case 'crit':           boundary = ';border:2px solid #3FB315';           break;         case 'mixed':           boundary = ';border:2px solid #4A57ED';           break;         case 'fumble':           boundary = ';border:2px solid #B31515';       }       return '<div class="showtip tipsy" title="' +         'Rolling ' + htmlReplace(roll.expression) + ' = ' + rollToText(roll.results) +         '"style="display:inline-block;min-width:1em;font-size:1.2em;' +         'font-weight:bold;padding:0px 3px;cursor:help' + boundary + '">' +         (roll.results.total || 0) + '</div>';     },     rollToText = function(roll) {       switch (roll.type) {         case 'R':           let c = (roll.mods && roll.mods.customCrit) || [{               comp: '==',               point: roll.sides             }],             f = (roll.mods && roll.mods.customFumble) || [{               comp: '==',               point: 1             }],             styledRolls = _.map(roll.results, function(r) {               let style = rollIsCrit(r.v, c[0].comp, c[0].point) ?                 ' critsuccess' :                 (rollIsCrit(r.v, f[0].comp, f[0].point) ?                   ' critfail' : '')               return `<span class='basicdiceroll${style}'>${r.v}</span>`;             });           return `(${styledRolls.join('+')})`;           break;         case 'M':           return roll.expr.toString().replace(/(\+|-)/g, '$1 ').replace(/\*/g, '&' + 'ast' + ';');           break;         case 'V':           return _.map(roll.rolls, rollToText).join(' ');           break;         case 'G':           return '(' + _.map(roll.rolls, a => _.map(a, rollToText).join(' '))             .join(' ') + ')';           break;         default:           return '';       }     },     detectCritical = function(roll) {       let s = [];       if (roll.type === 'V' && _.has(roll, 'rolls')) {         s = _.map(roll.rolls, detectCritical);       } else if (roll.type === 'G' && _.has(roll, 'rolls')) {         s = _.chain(roll.rolls)           .map(a => _.map(a, detectCritical))           .flatten()           .value();       } else if (roll.type === 'R' && _.has(roll, 'sides')) {         let crit = (roll.mods && roll.mods.customCrit) || [{           comp: '==',           point: roll.sides         }];         let fumble = (roll.mods && roll.mods.customFumble) || [{           comp: '==',           point: 1         }];         if (_.some(roll.results, r => rollIsCrit(r.v, crit[0].comp, crit[0].point))) {           s.push('crit');         }         if (_.some(roll.results, r => rollIsCrit(r.v, fumble[0].comp, fumble[0].point))) {           s.push('fumble');         }       }       let c = _.contains(s, 'crit');       let f = _.contains(s, 'fumble');       let m = _.contains(s, 'mixed') || (c && f);       return (m ? 'mixed' : (c ? 'crit' : (f ? 'fumble' : (false))));     },     rollIsCrit = function(value, comp, point) {       switch (comp) {         case '==':           return value == point;           break;         case '<=':           return value <= point;           break;         case '>=':           return value >= point;       }     },     htmlReplace = function(str) {       let entities = {         '<': 'lt',         '>': 'gt',         "'": '#39',         '@': '#64',         '{': '#123',         '|': '#124',         '}': '#125',         '[': '#91',         '"': 'quot',         ']': '#93',         '*': '#42'       };       return _.map(str.split(''), c => (_.has(entities, c)) ? ('&' + entities[c] + ';') : c)         .join('');     };   if (msg.content && msg.content.indexOf('!modattr --sel --training_progress') === 0 && msg.inlinerolls) {     let output = '/w GM Dice rolls were ' + msg.inlinerolls.map(roll => makeInlineroll(roll)).join(', ');     sendChat('API', output);   } });
Thanks Jakob! That does exactly what I was looking for. And thanks Silvyre! Once I saw my rolls, I could see the modifications I made to my macro weren't applying the groupings correctly, so I went with essentially what you posted. For anyone else trying to do this, here's the solution, which exists as an ability on a character sheet. !modattr --sel --training_progress#[[ { ?{Days|1}d20 + ?{Apply Attribute Bonus to Training?|None, 0|Strength, @{selected|strength_mod}|Dexterity, @{selected|dexterity_mod}|Constitution, @{selected|constitution_mod}|Intelligence, @{selected|intelligence_mod}|Wisdom, @{selected|wisdom_mod}|Charisma, @{selected|charisma_mod}} }>21f<10 + 2 * ?{Days} ]]
1497640287
Silvyre
Roll20 Mod Team
Awesome! Great work, Jakob, and happy rolling!