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

Listening for 5e OGL damage rolls?

Hi, I'm pretty new to scripting, but having upgraded to Pro and installed some API's I've fallen into the rabbit hole a bit. I'm trying to cook up a script that will listen for 5e OGL damage rolls and output a button to apply damage to a targeted token. I've got the principles figured out and the main mechanism of it working, but unfortunately the detection part is the one that's giving me a headache. I've borrowed the roll listen portion from the OGL Companion script, but it didn't work very well, so for debugging purposes I've pared it down to a simple check that outputs whether the roll is an attack with damage. For some reason it outputs "Not an attack" even when the roll is, in fact, an attack. Any help would be greatly appreciated, and of course once I've finished the damage application script I'll post it here! on("chat:message", function(msg) {     if (msg.rolltemplate) { if (["dmg"].indexOf(msg.rolltemplate) > -1) {sendChat("", "An attack");} if (["dmg"].indexOf(msg.rolltemplate) < 0) {sendChat("", "Not an attack");}     } }); Thanks in advance!
1570890435

Edited 1570916001
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Welcome to the dark side Krzysztof! For your snippet there, I'd recommend the following changes: on("chat:message", function(msg) {     if (msg.rolltemplate) {         if(/dmg/.test(msg.rolltemplate)){             sendChat('','An Attack');         }else{             sendChat('','not an attack');         }     } }); .test uses regex to examine the string and returns true if it matches the regex, and false if it doesn't. It also fails out quickly so it's reasonably performant. I'd also recommend using a clone of msg to avoid manipulating the msg object before it reaches other scripts. Knowing something of the 5e template, the other thing I might suggest is to not look for one of the dmg rolltemplates, but just look for the damage field (can't remember it's name atm). Something like this: on("chat:message", function(orig_msg) {     let msg = _.clone(orig_msg);     if (/{{dmg\d=/.test(msg.content)) {         log('an attack');     } }); or on("chat:message", function(orig_msg) {     let msg = _.clone(orig_msg),         damage;     if(msg.inlineRolls){         msg = extractRoll(msg);     }     msg.content.replace(/{{dmg\d=(.+?)}}/,(match,damageAmount)=>{         damage = damageAmount;     }); }); The extractRoll function is one that I stole from The Aaron to replace the inline roll references (e.g. $[[0]]) with the rolls actual result: extractRoll = function(msg){ return _.chain(msg.inlinerolls) .reduce(function(m,v,k){ m['$[['+k+']]']=v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } Hope that all helps.
Wow, thanks a bunch! This ought to save me some headaches. I'm new as hell to this, but it's coming together. I can't get the extractRoll function to work, but I had already prepared a clunkier version of my own that works with my script, so no big loss.
1570915967
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Ah, I goofed in my demo call of it. It should be: extractRoll(msg) Fixed it in my example code as well.
1571053752

Edited 1571054991
Annnnd it's alive! It's all a bit rough, but works fine for my purposes. It requires ChatSetAttr and AlterBars (although it could easily be modified to use TokenMod instead without any changes in the script, just swapping out the abilities), and relies on storing the damage as attributes a character named "Damage Tracker". To use it you have to set up a character with that name and input three abilities. Here's the code: extractRoll = function(msg){     return _.chain(msg.inlinerolls)         .reduce(function(m,v,k){             m['$[['+k+']]']=v.results.total || 0;             return m;         },{})         .reduce(function(m,v,k){             return m.replace(k,v);         },msg.content)         .value(); } findRollResult = function(msg, rollname, isString = 0){ let pattern = new RegExp('{{' + rollname + '=(.+?)}}'); let result = 0; if (isString > 0) { msg.content.replace(pattern,(match,rollResult)=>{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=>{ result = parseInt(rollResult); }); } return result; } on("chat:message", function(orig_msg) {     if (orig_msg.rolltemplate && orig_msg.inlinerolls) {         if(/{{dmg\d=/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, damageBase, damageCrit, atk1, atk2, critTarget, charName; damageBase = damageCrit = atk1 = atk2 = 0; damageType = charName = ''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=>{ charName = charname; }); damageType = findRollResult(msg, 'dmg1type', 1); damageBase = findRollResult(msg, 'dmg1') + findRollResult(msg, 'dmg2') + findRollResult(msg, 'hldmg');  damageCrit = damageBase + findRollResult(msg, 'crit1') + findRollResult(msg, 'crit2') + findRollResult(msg, 'hldmgcrit'); if (damageType == 'Healing') { sendChat('', '!setattr --silent --name Damage Tracker --lastHeal|' + damageBase); sendChat('', '[Apply Healing](!
%{Damage Tracker|ApplyHeal})'); } else { sendChat('', '!setattr --silent --name Damage Tracker --lastBaseDamage|' + damageBase); sendChat('', '!setattr --silent --name Damage Tracker --lastCritDamage|' + damageCrit); sendChat('', ' [Hit](!
%{Damage Tracker|ApplyDamage}) [Critical](!
%{Damage Tracker|ApplyDamageWithCrit}) '); } } } }); Then on the "Damage Tracker" character you need three abilities: ApplyDamage: !alter --target|@{target|token_id} --bar|1 --amount|-@{lastBaseDamage} ApplyDamageWithCrit !alter --target|@{target|token_id} --bar|1 --amount|-@{lastCritDamage} ApplyHeal !alter --target|@{target|token_id} --bar|1 --amount|@{lastHeal} Right now it can't detect whether the roll is a crit and outputs the buttons to all players, but the latter could be changed pretty easily and the former, well, I've spent too much time on this already instead of prepping my game, but I'll get around to it at some point :) Thanks again for the help!
1573759090

Edited 1573765028
o.O you just made my day we have been looking for something like this for ages! i just noticed one thing missing.  one of our rangers uses a lot of Global Damage modifiers, and this ignores them. luckily they way your wrote it, i just was able to add it in no problem where you calculate the last damage and last crit you have just: damageBase = findRollResult(msg, 'dmg1') + findRollResult(msg, 'dmg2') + findRollResult(msg, 'hldmg');  damageCrit = damageBase + findRollResult(msg, 'crit1') + findRollResult(msg, 'crit2') + findRollResult(msg, 'hldmgcrit'); Just changing it to this adds the global damages: damageBase = findRollResult(msg, 'dmg1') + findRollResult(msg, 'dmg2') + findRollResult(msg, 'hldmg') + findRollResult(msg, 'globaldamage'); damageCrit = damageBase + findRollResult(msg, 'crit1') + findRollResult(msg, 'crit2') + findRollResult(msg, 'hldmgcrit') + findRollResult(msg, 'globaldamagecrit'); and for those that use token Mod the macros are: ApplyDamage !token-mod --ids @{target|token_id} --set bar1_value|-@{lastBaseDamage} ApplyDamageWithCrit !token-mod --ids @{target|token_id} --set bar1_value|-@{lastCritDamage} ApplyHeal !token-mod --ids @{target|token_id} --set bar1_value|+@{lastHeal}
1573774316

Edited 1573774332
Currently i'm attempting to alter this from an older post from The Aaron to try and add the crit check on('ready',function(){     'use strict'; // builds a comparison function for all the crit rules   var getCritComparitor = function(roll){     let comp=[]; // handle explicit custom rules     if(_.has(roll,'mods') && _.has(roll.mods,'customCrit')){       _.each(roll.mods.customCrit,function(cc){         switch(cc.comp){           case '<=':comp.push((o)=>o<=cc.point);break;           case '==':comp.push((o)=>o==cc.point);break;           case '>=':comp.push((o)=>o>=cc.point);break;         }       });     } else { // default "max value" rule       comp.push((o)=>o==roll.sides);     }  // return a comparison function that checks each rule on a value     return function(v){       let crit=false;       _.find(comp,(f)=>crit=crit||f(v));             return crit;     };   };   var isCrit = function(roll){ // builds a comparison function for crits in this roll type     let comp=getCritComparitor(roll);     _.each(roll.results,(r)=>{ // check each value with the comparison function       if(comp(r.v)){ // If it was a crit, report it to chat // (replace with what you want or return true here and false outside the if for an inspection function)         sendChat('isCrit',`The ${r.v} is a critical!`);       }     });   };   on('chat:message',function(msg){     if(msg.inlinerolls){ // for each inline roll // call isCrit() on each each entry in rolls       _.each(msg.inlinerolls,(ir)=>_.each(ir.results.rolls,(irrr)=>isCrit(irrr)));     }   }); }); It detects the crits, but it also triggers on max damage rolls, so some tweaking is still needed, then to get it added to the other. here is one of my test rolls, i lowered my Crit Range the main roll is the 13, but it also triggered on the max d6 roll:
this is a bit beyond me.. i was able to get it to only trigger on d20 rolls, but i couldn't get it to help out at all with which button to put in Chat, hopefully someone else can figure out the 'did it crit' part of this.
Well i suck with it, and finally figured it out :D extractRoll = function(msg){ return _.chain(msg.inlinerolls) .reduce(function(m,v,k){ m['$[['+k+']]']=v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } findRollResult = function(msg, rollname, isString = 0){ let pattern = new RegExp('{{' + rollname + '=(.+?)}}'); let result = 0; if (isString > 0) { msg.content.replace(pattern,(match,rollResult)=>{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=>{ result = parseInt(rollResult); }); } return result; } // builds a comparison function for all the crit rules var getCritComparitor = function(roll){ let comp=[]; // handle explicit custom rules if(_.has(roll,'mods') && _.has(roll.mods,'customCrit')){ _.each(roll.mods.customCrit,function(cc){ switch(cc.comp){ case '<=':comp.push((o)=>o<=cc.point);break; case '==':comp.push((o)=>o==cc.point);break; case '>=':comp.push((o)=>o>=cc.point);break; } }); } else { // default "max value" rule comp.push((o)=>o==roll.sides); } // return a comparison function that checks each rule on a value return function(v){ let crit=false; _.find(comp,(f)=>crit=crit||f(v)); return crit; }; }; var isCrit = function(roll){ var crits = 0; // builds a comparison function for crits in this roll type if (roll.sides == 20) { let comp=getCritComparitor(roll); _.each(roll.results,(r)=>{ // check each value with the comparison function if(comp(r.v)){ // If it was a crit, report it to chat // (replace with what you want or return true here and false outside the if for an inspection function) // sendChat('isCrit',`The ${r.v} is a critical!`); crits += 1; } }); } return crits; }; on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate && orig_msg.inlinerolls) { if(/{{dmg\d=/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, damageBase, damageCrit, atk1, atk2, critTarget, charName; damageBase = damageCrit = atk1 = atk2 = crits = 0; damageType = charName = advantage = normal = disadvantage = ''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=>{ charName = charname; }); damageType = findRollResult(msg, 'dmg1type', 1); damageBase = findRollResult(msg, 'dmg1') + findRollResult(msg, 'dmg2') + findRollResult(msg, 'hldmg') + findRollResult(msg, 'globaldamage'); damageCrit = damageBase + findRollResult(msg, 'crit1') + findRollResult(msg, 'crit2') + findRollResult(msg, 'hldmgcrit') + findRollResult(msg, 'globaldamagecrit'); advantage = findRollResult(msg, 'advantage'); normal = findRollResult(msg, 'normal'); disadvantage = findRollResult(msg, 'disadvantage'); _.each(msg.inlinerolls,(ir)=>_.each(ir.results.rolls,(irrr)=>{ if (irrr.sides == 20) crits +=isCrit(irrr); })); if (damageType == 'Healing') { sendChat('', '!setattr --silent --name Damage Tracker --lastHeal|' + damageBase); sendChat('', '/w gm [Apply Healing](!
%{Damage Tracker|ApplyHeal})'); sendChat('','Total: '+damageBase); } else { sendChat('', '!setattr --silent --name Damage Tracker --lastBaseDamage|' + damageBase); sendChat('', '!setattr --silent --name Damage Tracker --lastCritDamage|' + damageCrit); // if it's normal or advantage, and 1 crit roll is detected, it's a crit // if it's disadvantage 2 crits are needed for it to be a crit. if ( (((normal == 1) || (advantage == 1)) && (crits >= 1)) || ((disadvantage == 1) && (crits >= 2)) ) { //whispers the button to the GM sendChat('Damage', '/w gm [Critical](!
%{Damage Tracker|ApplyDamageWithCrit}) '); //Also adds it up for the players sendChat('',' Total Crit: ' + damageCrit); } else { sendChat('Damage', '/w gm [Normal](!
%{Damage Tracker|ApplyDamage})'); sendChat('','Total: ' + damageBase); } } } } }); It also sends the apply damage buttons just to the GM so no one else clicks it, but it lets everyone know the total.
1573853774
The Aaron
Roll20 Production Team
API Scripter
Great!  I was just going to take a look at it, but now I don't need to!  Good job working it out!!
Was able to update the macro to count for Resistance/Vulnerability.  Not sure if there is a better way to add it in. But I really like this so far.   !token-mod --ids @{target|token_id} --set bar1_value|-[[@{lastBaseDamage}/?{Resistance?|No,1|Yes,2}*?{Vulnerability?|No,1|Yes,2}]] !token-mod --ids @{target|token_id} --set bar1_value|-[[@{lastCritDamage}/?{Resistance?|No,1|Yes,2}*?{Vulnerability?|No,1|Yes,2}]]
I was thinking of maybe adding Resistance etc, but it would only work for like full reistance, like a Barbarian Bear totem enemy, but the issue with split resistance. so if they take full damage from Pierce, but resistant to Cold, it wouldn't work on an attack like this. and im' not sure if it would be as helpful to make a button per damage type, that would be a fair bit of clicking for the GM.
That is something that could happen... 
If you are going to use them add floor so it rounds down correctly, and you dont end up with .5 hp's !token-mod --ids @{target|token_id} --set bar1_value|-[[floor(*@{lastBaseDamage}/?{Resistance?|No,1|Yes,2}*?{Vulnerability?|No,1|Yes,2}]] !token-mod --ids @{target|token_id} --set bar1_value|-[[floor(@{lastCritDamage}/?{Resistance?|No,1|Yes,2})*?{Vulnerability?|No,1|Yes,2}]] asked my gm, he wanted just the resistance one, because Vulnerability is so rare, and honestly you can just apply damage twice by clicking twice. so i'm using: Normal: !token-mod --ids @{target|token_id} --set bar1_value|-[[floor(@{lastBaseDamage}/?{Resistance?|No,1|Yes,2})]] Crit: !token-mod --ids @{target|token_id} --set bar1_value|-[[floor(@{lastCritDamage}/?{Resistance?|No,1|Yes,2})]]
Good points, much cleaner as well.
1574369731

Edited 1574370393
I was thinking about this a bit more, after our last sessions in a lvl14 campaign.  One of our casters blasted out 3 Eldritch Blast shots in a row. Using the current Macro's on a Character sheet doesn't keep track of old hits, just the most recent.  so I went back and tweaked it to remove the need for a Damage Tracker char, and the macros all together, and baked it into the chat button, so it retains the damage/heal/crit value, as you can see here, i can go back and hit the old buttons, and they still apply correct #'s for that hit. In testing it, i also realized i had missed out on the healing one, it could over heal, so i changed it to cap out at their max HP.  in the above gif, you can see me heal them twice, but it doesn't go over 172. so here is the newest script, without the need of having to add macro's or a character to hold the macros. extractRoll = function(msg){ return _.chain(msg.inlinerolls) .reduce(function(m,v,k){ m['$[['+k+']]']=v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } findRollResult = function(msg, rollname, isString = 0){ let pattern = new RegExp('{{' + rollname + '=(.+?)}}'); let result = 0; if (isString > 0) { msg.content.replace(pattern,(match,rollResult)=>{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=>{ result = parseInt(rollResult); }); } return result; } // builds a comparison function for all the crit rules var getCritComparitor = function(roll){ let comp=[]; // handle explicit custom rules if(_.has(roll,'mods') && _.has(roll.mods,'customCrit')){ _.each(roll.mods.customCrit,function(cc){ switch(cc.comp){ case '<=':comp.push((o)=>o<=cc.point);break; case '==':comp.push((o)=>o==cc.point);break; case '>=':comp.push((o)=>o>=cc.point);break; } }); } else { // default "max value" rule comp.push((o)=>o==roll.sides); } // return a comparison function that checks each rule on a value return function(v){ let crit=false; _.find(comp,(f)=>crit=crit||f(v)); return crit; }; }; var isCrit = function(roll){ var crits = 0; // builds a comparison function for crits in this roll type if (roll.sides == 20) { let comp=getCritComparitor(roll); _.each(roll.results,(r)=>{ // check each value with the comparison function if(comp(r.v)){ // If it was a crit, report it to chat // (replace with what you want or return true here and false outside the if for an inspection function) // sendChat('isCrit',`The ${r.v} is a critical!`); crits += 1; } }); } return crits; }; on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate && orig_msg.inlinerolls) { if(/{{dmg\d=/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, damageBase, damageCrit, atk1, atk2, critTarget, charName; damageBase = damageCrit = atk1 = atk2 = crits = 0; damageType = charName = advantage = normal = disadvantage = ''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=>{ charName = charname; }); damageType = findRollResult(msg, 'dmg1type', 1); damageBase = findRollResult(msg, 'dmg1') + findRollResult(msg, 'dmg2') + findRollResult(msg, 'hldmg') + findRollResult(msg, 'globaldamage'); damageCrit = damageBase + findRollResult(msg, 'crit1') + findRollResult(msg, 'crit2') + findRollResult(msg, 'hldmgcrit') + findRollResult(msg, 'globaldamagecrit'); advantage = findRollResult(msg, 'advantage'); normal = findRollResult(msg, 'normal'); disadvantage = findRollResult(msg, 'disadvantage'); _.each(msg.inlinerolls,(ir)=>_.each(ir.results.rolls,(irrr)=>{ if (irrr.sides == 20) crits +=isCrit(irrr); })); if (damageType == 'Healing') { //whispers the button to the GM sendChat('Healing','/w gm [Healing](!token-mod --ids @{target|1|token_id} --set bar1_value|[[{@{target|1|bar1}+'+damageBase+'+ 0d0,@{target|1|bar1|max}+0d0}kl1]])'); //Also adds it up for the players sendChat('','Total: '+damageBase); } else { // if it's normal or advantage, and 1 crit roll is detected, it's a crit // if it's disadvantage 2 crits are needed for it to be a crit. if ( ((normal || advantage) && (crits >= 1)) || (disadvantage && (crits >= 2)) ) { //whispers the button to the GM sendChat('Damage','/w gm [Critical](!token-mod --ids @{target|token_id} --set bar1_value|-[[floor('+damageCrit+'/?{Resistance|No,1|Yes,2})]])'); //Also adds it up for the players sendChat('',' Total Crit: ' + damageCrit); } else { //whispers the button to the GM sendChat('Damage','/w gm [Normal](!token-mod --ids @{target|token_id} --set bar1_value|-[[floor('+damageBase+'/?{Resistance|No,1|Yes,2})]])'); //Also adds it up for the players sendChat('','Total: ' + damageBase); } } } } });
This is great, thank you for sharing! It isn't detecting a crit roll for me though: Sheets are configured to always roll second d20 and I use the left column for all normal rolls, is that consistent with how you have the script coded?
1574552384

Edited 1574553384
hmm, i honestly didn't try it with that rolling style, i use Query Advantage.  the issue with the Always roll advantage, is there is no way for the script to know which style was intended, like say that was actually rolled with disadvantage, the 24 would be the actual value, and not a crit. so since it wont know the circumstance, i added that if you use that roll style, if at least one crit is detected, it shows both buttons, so you can pick the right one for your circumstance.  if 2 crit rolls, just the crit button. updated to take that into account: extractRoll = function(msg){ return _.chain(msg.inlinerolls) .reduce(function(m,v,k){ m['$[['+k+']]']=v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } findRollResult = function(msg, rollname, isString = 0){ let pattern = new RegExp('{{' + rollname + '=(.+?)}}'); let result = 0; if (isString > 0) { msg.content.replace(pattern,(match,rollResult)=>{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=>{ result = parseInt(rollResult); }); } return result; } // builds a comparison function for all the crit rules var getCritComparitor = function(roll){ let comp=[]; // handle explicit custom rules if(_.has(roll,'mods') && _.has(roll.mods,'customCrit')){ _.each(roll.mods.customCrit,function(cc){ switch(cc.comp){ case '<=':comp.push((o)=>o<=cc.point);break; case '==':comp.push((o)=>o==cc.point);break; case '>=':comp.push((o)=>o>=cc.point);break; } }); } else { // default "max value" rule comp.push((o)=>o==roll.sides); } // return a comparison function that checks each rule on a value return function(v){ let crit=false; _.find(comp,(f)=>crit=crit||f(v)); return crit; }; }; var isCrit = function(roll){ var crits = 0; // builds a comparison function for crits in this roll type if (roll.sides == 20) { let comp=getCritComparitor(roll); _.each(roll.results,(r)=>{ // check each value with the comparison function if(comp(r.v)){ // If it was a crit, report it to chat // (replace with what you want or return true here and false outside the if for an inspection function) // sendChat('isCrit',`The ${r.v} is a critical!`); crits += 1; } }); } return crits; }; on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate && orig_msg.inlinerolls) { if(/{{dmg\d=/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, damageBase, damageCrit, atk1, atk2, critTarget, charName; damageBase = damageCrit = atk1 = atk2 = crits = 0; damageType = charName = advantage = normal = disadvantage = always = critBtn = critDmg =''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=>{ charName = charname; }); damageType = findRollResult(msg, 'dmg1type', 1); damageBase = findRollResult(msg, 'dmg1') + findRollResult(msg, 'dmg2') + findRollResult(msg, 'hldmg') + findRollResult(msg, 'globaldamage'); damageCrit = damageBase + findRollResult(msg, 'crit1') + findRollResult(msg, 'crit2') + findRollResult(msg, 'hldmgcrit') + findRollResult(msg, 'globaldamagecrit'); advantage = findRollResult(msg, 'advantage'); normal = findRollResult(msg, 'normal'); disadvantage = findRollResult(msg, 'disadvantage'); always = findRollResult(msg, 'always'); _.each(msg.inlinerolls,(ir)=>_.each(ir.results.rolls,(irrr)=>{ if (irrr.sides == 20) crits +=isCrit(irrr); })); if (damageType == 'Healing') { //whispers the button to the GM sendChat('Healing','/w gm [Healing](!token-mod --ids @{target|1|token_id} --set bar1_value|[[{@{target|1|bar1}+'+damageBase+'+ 0d0,@{target|1|bar1|max}+0d0}kl1]])'); //Also adds it up for the players sendChat('','Total: '+damageBase); } else { // if it's normal or advantage, and 1 crit roll is detected, it's a crit // if it's disadvantage 2 crits are needed for it to be a crit. // if they always roll advantage and 2 crits are detected, only show crit button if ( ((normal || advantage) && (crits >= 1)) || ((disadvantage || always) && (crits >= 2)) ) { //whispers the button to the GM sendChat('Damage','/w gm [Critical](!token-mod --ids @{target|token_id} --set bar1_value|-[[floor('+damageCrit+'/?{Resistance|No,1|Yes,2})]])'); //Also adds it up for the players sendChat('',' Total Crit: ' + damageCrit); } else { //if they use the Always Roll Advantage and 1 crit is detected, add the crit button as option if (always && (crits >= 1)) { critBtn = '[Critical](!token-mod --ids @{target|token_id} --set bar1_value|-[[floor('+damageCrit+'/?{Resistance|No,1|Yes,2})]])'; critDmg = ' Total Crit: ' + damageCrit; } //whispers the button to the GM sendChat('Damage','/w gm [Normal](!token-mod --ids @{target|token_id} --set bar1_value|-[[floor('+damageBase+'/?{Resistance|No,1|Yes,2})]])' + critBtn); //Also adds it up for the players sendChat('','Total: ' + damageBase + critDmg); } } } } });