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

Help with modifying Damage Listener script

1587446241

Edited 1587491512
I've started modifying this script to listen for damage rolls from the Pathfinder Second Edition by Roll20 sheet. But right now I'm stumped and hoping someone can spot what I'm overlooking! Two things I'm having trouble figuring out: Only the first inline roll in the Additional Damage field ('roll04') gets pulled, so if the field contains '[[1d6]] fire, [[1d4]] electricity', only the result of the first inline roll is added to the total; I haven't been able to figure out how to get the results of all inline rolls in the section and combine them If 'roll04' is empty, as it is for most attacks, the lastBaseDamage and lastCritDamage attributes get filled with 'NaN' instead of a value; I need it to ignore 'roll04' if it's empty so it can still take the value from 'roll02' Here's what I have so far (thank you, The Aaron, for the help!): 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 &gt; 0) { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = parseInt(rollResult); }); } return result; } on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate &amp;&amp; orig_msg.inlinerolls) { if(/{{roll\d+_type=damage/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, saveType, damagePlus, damageBase, damageHeal, damageCrit, damageHalf, atk1, atk2, critTarget, charName; damagePlus = damageBase = damageHeal = damageCrit = damageHalf = atk1 = atk2 = 0; damageType = saveType = charName = ''; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=&gt;{ charName = charname; }); damageType = findRollResult(msg, 'roll02_info', 1); damagePlus = findRollResult(msg, 'roll04'); damageBase = findRollResult(msg, 'roll02') + damagePlus; damageHeal = findRollResult(msg, 'roll02'); damageCrit = damageBase * 2; if (damageType == 'Heal') { sendChat('', '!setattr --silent --name Damage Tracker --lastHeal|' + damageHeal); sendChat('', '!setattr --silent --name Damage Tracker --lastLohdmg|' + damagePlus); sendChat('', '/w gm [Apply Healing](!&amp;#13;&amp;#37;{Damage Tracker|ApplyHeal}) | [Damage Undead](!&amp;#13;&amp;#37;{Damage Tracker|DamageUndead}) | [*Lay on Hands* Damage](!&amp;#13;&amp;#37;{Damage Tracker|DamageLoH})'); } else { sendChat('', '!setattr --silent --name Damage Tracker --lastBaseDamage|' + damageBase); sendChat('', '!setattr --silent --name Damage Tracker --lastCritDamage|' + damageCrit); sendChat('', '/w gm [Hit](!&amp;#13;&amp;#37;{Damage Tracker|ApplyDamage}) | [Critical](!&amp;#13;&amp;#37;{Damage Tracker|ApplyDamageWithCrit})'); } } } }); Here's a sample macro with multiple inline rolls in 'roll04' (near the end, bolded ): @{Shezukoatl|whispertype} &amp;{template:rolls} {{limit_height=@{Shezukoatl|roll_limit_height}}} {{charactername=@{Shezukoatl|character_name} }} {{header=Scythe—Rage}} {{subheader=^{melee_strike}}} {{notes_show=@{Shezukoatl|roll_show_notes} }} {{notes=**DEADLY:** [**+1d10**](`&amp;#47;r 1d10) dmg on crit}} {{roll01_name=^{attack} }} {{roll01=[[1d20cs20cf1 + [ ] 4[@{Shezukoatl|text_modifier}] + (@{Shezukoatl|query_roll_bonus})[@{Shezukoatl|text_bonus}]]]}} {{roll01_type=attack }} {{roll01_info=Deadly d10 | Trip}} {{roll01_critical=1}} {{roll02_name=^{damage}}} {{roll02=[[1D10 + 4[@{Shezukoatl|text_ability_modifier}] + 0[WEAPON SPECIALIZATION] + 0[TEMP] + 0[OTHER] + @{Shezukoatl|query_roll_damage_bonus}[@{Shezukoatl|text_roll_damage_bonus}]]] }} {{roll02_type=damage}} {{roll02_info=slashing}} {{roll04_name=^{damage_additional}}} {{roll04=[[2]] electricity, [[1d3]] fire}} {{roll04_type=damage}} Here's one with no inline rolls in 'roll04' (near the end, bolded ): @{The Amalgam|whispertype} &amp;{template:rolls} {{limit_height=@{The Amalgam|roll_limit_height}}} {{charactername=@{The Amalgam|character_name} }} {{header=claw}} {{subheader=^{melee_strike}}} {{notes_show=@{The Amalgam|roll_show_notes}}} {{notes=}} {{roll01_name=^{attack} }} {{roll01=[[1d20cs20cf1 + (14[@{The Amalgam|text_modifier}]) + (@{The Amalgam|query_roll_bonus})[@{The Amalgam|text_bonus}]]]}} {{roll01_type=attack }} {{roll01_info=}} {{roll01_critical=1}} {{roll02_name=^{damage}}} {{roll02=[[1d8+5 + @{The Amalgam|query_roll_damage_bonus}[@{The Amalgam|text_roll_damage_bonus}]]] }} {{roll02_type=damage}} {{roll02_info=slashing}} {{roll04_name=^{damage_additional}}} {{roll04=}} {{roll04_type=damage}} Original script:&nbsp; <a href="https://app.roll20.net/forum/permalink/7836309/" rel="nofollow">https://app.roll20.net/forum/permalink/7836309/</a>
1587479012
The Aaron
Roll20 Production Team
API Scripter
The parsing in the original script is a little simplistic.&nbsp; It's expecting roll04 to contain just a number, which it then passes to parseInt().&nbsp; However, you'll have something like "2 electricity, 3 fire".&nbsp; parseInt() will only look at the first sequence of digits and return that.&nbsp; You can use this slightly less simplistic (but still flawed for some edge cases: findRollResult = function(msg, rollname, isString = 0){ let pattern = new RegExp('{{' + rollname + '=(.+?)}}'); let result = 0; if (isString &gt; 0) { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=&gt;{ &nbsp;&nbsp; &nbsp;&nbsp;result = rollResult.match(/\d+/g).reduce((m,n)=&gt;m+parseInt(n),0); }); } return result; } That function is used to extract text and numbers, it would be better if it did one or the other, but you can deal with the NaN issue where you call it by using this: damagePlus = findRollResult(msg, 'roll04') ||0 ; Hope that helps.
1587489849

Edited 1587491457
Thanks! That got it to add all inline rolls in the roll04 section! =D And I'm not getting NaN results when that section is empty, but instead I'm getting some weird erratic behavior; one of these will happen randomly: the lastBaseDamage and lastCritDamage attributes don't get changed at all they get changed but treating the damageBase value as 4 higher every time (could it be treating the 4 in roll04 as a value to include?) lastBaseDamage is unchanged while lastCritDamage changes, still treating the damageBase value as 4 higher.
1587524217

Edited 1587524659
I realized I accidentally put the ||0 inside the findRollResult instead of outside, but fixing that didn't seem to make a difference. I've made some more adjustments, including adding -4 to damageBase to account for the random +4, but it still is listening very unreliably... After a few tries, the attributes just stop getting changed. Here's where I'm at now: 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 &gt; 0) { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult.match(/\d+/g).reduce((m,n)=&gt;m+parseInt(n),0); }); } return result; } on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate &amp;&amp; orig_msg.inlinerolls) { if(/{{roll\d+_type=damage/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, saveType, damagePlus, damageBase, damageHeal, damageCrit, damageHalf, atk1, atk2, critTarget, charName; damagePlus = damageBase = damageHeal = damageCrit = damageHalf = atk1 = atk2 = 0; damageType = saveType = charName = ''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=&gt;{ charName = charname; }); damageType = findRollResult(msg, 'roll02_info', 1); damagePlus = findRollResult(msg, 'roll04')||0; damageBase = findRollResult(msg, 'roll02') + damagePlus - 4; damageHeal = findRollResult(msg, 'roll02'); damageCrit = damageBase * 2; if (damageType == 'Heal') { sendChat('', '!setattr --silent --name Damage Tracker --lastHeal|' + damageHeal + ' --lastLohdmg|' + damagePlus); sendChat('', '/w gm [Apply Healing](~Damage Tracker|ApplyHeal) | [Damage Undead](~Damage Tracker|DamageUndead) | [*Lay on Hands* Damage](~Damage Tracker|DamageLoH)'); } else { sendChat('', '!setattr --silent --name Damage Tracker --lastBaseDamage|' + damageBase + ' --lastCritDamage|' + damageCrit); sendChat('', '/w gm [Hit](~Damage Tracker|ApplyDamage) | [Critical](~Damage Tracker|ApplyDamageWithCrit)'); } } } });
1587605207

Edited 1587605268
So I tested again, taking damagePlus out of damageBase altogether, and it changes the attributes just fine until I roll a Heal effect that has an empty roll04. Then it starts setting lastLohdmg to 4, and eventually stops changing attributes at all. Is there an alternative to this damagePlus = findRollResult(msg, 'roll04') ||0 ; that will prevent an empty roll04 from being treated as a non-value and breaking any math it's used in?
1587657359

Edited 1587657735
Anyone know if there's something I could add to this line to make an empty roll equal zero as well, not just strings? Idk if something like isEmpty = 0 would work, but I'm gonna test it when I have a chance.. findRollResult = function(msg, rollname, isString = 0){
1587658666
The Aaron
Roll20 Production Team
API Scripter
Hmm... maybe try: damagePlus = parseInt(findRollResult(msg, 'roll04')) ||0 ; It might be easier to write a fresh script than to try and adjust this one.&nbsp; It seems like there's a need for a generic implementation that could be configured to how to process the fields from Roll Templates.&nbsp; Maybe just creating something that can understand Roll Template syntax and provide an interface to dealing with them.&nbsp; That's something I could take a stab at.
1587668819

Edited 1587668946
I'll test that out, too, thanks! I'm much better at adjusting existing scripts than writing them from scratch ( still room for improvement as you can see :p ), so if you're up to trying that out, I can invite you to my API test game
But I'll see how the two tweaks fare, first.
Still having the same issues after trying each change :/
1587796732

Edited 1587796969
Okay I've got it to a point where, aside from the !setattr commands not always firing, the only big issue is that roll04 is treated as having a value of 4 when it's empty. I think when it has no inline roll to parse, it somehow grabs the 4 from 'roll04', and I'm not sure how to prevent that. 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 &gt; 0) { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult.match(/\d+/g).reduce((m,n)=&gt;m+parseInt(n),0); }); } return result; } on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate &amp;&amp; orig_msg.inlinerolls) { if(/{{roll02_type=damage/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, saveType, damagePlus, damageBase, damageHeal, damageCrit, damageHalf, atk1, atk2, critTarget, charName; damagePlus = damageBase = damageHeal = damageCrit = damageHalf = atk1 = atk2 = 0; damageType = saveType = charName = ''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=&gt;{ charName = charname; }); damageType = findRollResult(msg, 'roll02_info', 1); saveType = findRollResult(msg, 'savedc', 1); damagePlus = findRollResult(msg, 'roll04'); damageBase = findRollResult(msg, 'roll02') + damagePlus; damageHeal = findRollResult(msg, 'roll02'); damageCrit = damageBase * 2; if (damageType.match(/^(Heal|heal|Healing|healing|Positive|positive)$/)) { sendChat('', '!setattr --name Damage Tracker --lastHeal|' + damageHeal + ' --lastLohdmg|' + damagePlus + '\n/w gm [Apply Healing](~Damage Tracker|ApplyHeal) | [Damage Undead](~Damage Tracker|DamageUndead) | [*Lay on Hands* Damage](~Damage Tracker|DamageLoH)'); } else if (saveType &gt; 0) { sendChat('', '!setattr --name Damage Tracker --lastBaseDamage|' + damageBase + ' --lastCritDamage|' + damageCrit + ' --lastHalfDamage|' + damageHalf + '\n/w gm [Full Damage](~Damage Tracker|ApplyDamage) | [Half Damage](~Damage Tracker|ApplyDamageHalf) | [Double Damage](~Damage Tracker|ApplyDamageWithCrit)'); } else { sendChat('', '!setattr --name Damage Tracker --lastBaseDamage|' + damageBase + ' --lastCritDamage|' + damageCrit + '\n/w gm [Hit](~Damage Tracker|ApplyDamage) | [Critical](~Damage Tracker|ApplyDamageWithCrit)'); } } } });
1587880171

Edited 1587882105
Got it working! Someone helped me come up with a fix to replace the empty 'roll04' with a [[0]]. The !setattr commands are still a little hit and miss, so I got rid of the --silent function so I can see in chat when they work. Thanks for helping me get this far, The Aaron! In case anyone wants to use it, the script itself relies on ChatSetAttr while the abilities use TokenMod: 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 fix = (text)=&gt; { if(text.includes('{{roll04=}}')) { text = text.replace('{{roll04=}}', '{{roll04=[[0]]}}'); return fix(text); } else{ return text; } } msg.content = fix(msg.content) let pattern = new RegExp('{{' + rollname + '=(.+?)}}'); let result = 0; if (isString &gt; 0) { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult; }); } else { msg.content.replace(pattern,(match,rollResult)=&gt;{ result = rollResult.match(/\d+/g).reduce((m,n)=&gt;m+parseInt(n),0); }); } return result; } on("chat:message", function(orig_msg) { if (orig_msg.rolltemplate &amp;&amp; orig_msg.inlinerolls) { if(/{{roll02_type=damage/.test(orig_msg.content)){ let msg = _.clone(orig_msg), damageType, saveType, damagePlus, damageBase, charName; damagePlus = damageBase = 0; damageType = saveType = charName = ''; msg.content = extractRoll(msg); msg.content.replace(/charname=(.+?)$/,(match,charname)=&gt;{ charName = charname; }); damageType = findRollResult(msg, 'roll02_info', 1); saveType = findRollResult(msg, 'savedc', 1); damagePlus = findRollResult(msg, 'roll04'); damageBase = findRollResult(msg, 'roll02'); if (saveType &gt; 0 &amp;&amp; !damageType.match(/^(HEAL|Heal|heal|HEALING|Healing|healing|POSITIVE|Positive|positive)$/)) { sendChat('', '!setattr --name Damage Tracker --lastBaseDamage|' + (damageBase + damagePlus) + '\n/w gm [Full Damage](~Damage Tracker|ApplyDamage) | [Half Damage](~Damage Tracker|ApplyDamageHalf) | [Double Damage](~Damage Tracker|ApplyDamageDouble)'); } else if (damageType.match(/^(HEAL|Heal|heal|HEALING|Healing|healing|POSITIVE|Positive|positive)$/)) { sendChat('', '!setattr --name Damage Tracker --lastHeal|' + damageBase + ' --lastLohdmg|' + damagePlus + '\n/w gm [Apply Healing](~Damage Tracker|ApplyHeal) | [Full Damage](~Damage Tracker|DamageUndeadFull) | [Half Damage](~Damage Tracker|DamageUndeadHalf) | [Double Damage](~Damage Tracker|DamageUndeadDouble) | [*Lay on Hands* Full Damage](~Damage Tracker|ApplyDamageLohFull) | [*Lay on Hands* Half Damage](~Damage Tracker|ApplyDamageLohHalf) | [*Lay on Hands* Double Damage](~Damage Tracker|ApplyDamageLohDouble)'); } else { sendChat('', '!setattr --name Damage Tracker --lastBaseDamage|' + (damageBase + damagePlus) + '\n/w gm [Hit](~Damage Tracker|ApplyDamage) | [Critical](~Damage Tracker|ApplyDamageWithCrit)'); } } } }); Uses a character called Damage Tracker with the following abilities, which deal the damage to bar2 (temporary HP) and bar1 (normal HP) , so adjust them to suit your game. Assumes the spell lay on hands has the healing value in the main damage field and the damaging roll in Additional Damage: ApplyDamage !token-mod --ids @{target|Damage|token_id} --set bar2_value|-[[{@{lastBaseDamage}+[[?{Weakness|0}]]-[[?{Resistance|0}]],@{target|Damage|bar2}}kl1]] bar1_value|-[[{@{lastBaseDamage}+[[?{Weakness}]]-[[?{Resistance}]]-@{target|Damage|bar2},0}kh1]] ApplyDamageWithCrit !token-mod --ids @{target|Damage|token_id} --set bar2_value|-[[{(@{lastBaseDamage}*2)+[[?{Deadly|0}]]+[[?{Weakness|0}]]-[[?{Resistance|0}]],@{target|Damage|bar2}}kl1]] bar1_value|-[[{(@{lastBaseDamage}*2)+[[?{Deadly}]]+[[?{Weakness}]]-[[?{Resistance}]]-@{target|Damage|bar2},0}kh1]] ApplyDamageHalf !token-mod --ids @{target|Damage|token_id} --set bar2_value|-[[{floor(@{lastBaseDamage}/2)+[[?{Weakness|0}]]-[[?{Resistance|0}]],@{target|Damage|bar2}}kl1]] bar1_value|-[[{floor(@{lastBaseDamage}/2)+[[?{Weakness}]]-[[?{Resistance}]]-@{target|Damage|bar2},0}kh1]] ApplyDamageDouble !token-mod --ids @{target|Damage|token_id} --set bar2_value|-[[{(@{lastBaseDamage}*2)+[[?{Weakness|0}]]-[[?{Resistance|0}]],@{target|Damage|bar2}}kl1]] bar1_value|-[[{(@{lastBaseDamage}*2)+[[?{Weakness}]]-[[?{Resistance}]]-@{target|Damage|bar2},0}kh1]] ApplyHeal !token-mod --ids @{target|Heal|token_id} --set bar1_value|+[[{@{lastHeal}, @{target|Heal|bar1|max}-@{target|Heal|bar1}}kl1]] DamageUndeadFull !token-mod --ids @{target|Undead|token_id} --set bar2_value|-[[{@{lastHeal},@{target|Undead|bar2}}kl1]] bar1_value|-[[{@{lastHeal}-@{target|Undead|bar2},0}kh1]] DamageUndeadHalf !token-mod --ids @{target|Undead|token_id} --set bar2_value|-[[{floor(@{lastHeal}/2),@{target|Undead|bar2}}kl1]] bar1_value|-[[{floor(@{lastHeal}/2)-@{target|Undead|bar2},0}kh1]] DamageUndeadDouble !token-mod --ids @{target|Undead|token_id} --set bar2_value|-[[{(@{lastHeal}*2),@{target|Undead|bar2}}kl1]] bar1_value|-[[{(@{lastHeal}*2)-@{target|Undead|bar2},0}kh1]] DamageLohFull !token-mod --ids @{target|Undead|token_id} --set bar2_value|-[[{@{lastLohdmg},@{target|Undead|bar2}}kl1]] bar1_value|-[[{@{lastLohdmg}-@{target|Undead|bar2},0}kh1]] DamageLohHalf !token-mod --ids @{target|Undead|token_id} --set bar2_value|-[[{floor(@{lastLohdmg}/2),@{target|Undead|bar2}}kl1]] bar1_value|-[[{floor(@{lastLohdmg}/2)-@{target|Undead|bar2},0}kh1]] DamageLohDouble !token-mod --ids @{target|Undead|token_id} --set bar2_value|-[[{(@{lastLohdmg}*2),@{target|Undead|bar2}}kl1]] bar1_value|-[[{(@{lastLohdmg}*2)-@{target|Undead|bar2},0}kh1]]
1587882441
The Aaron
Roll20 Production Team
API Scripter
Nice! &nbsp;Glad to see you got it to all come together!
Yeah! I'm hoping the !setattr commands not always firing is only a side effect of the issues the API is having lately. I'm not sure why else they don't always get sent to chat.