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

Checking for multiple success targets on the same roll

Hey there.  Infinity RPG uses a 2d20 system with 2 methods of success: 1. Rolling 1 or more dice under/ equalling the character’s skill level + relevant attribute 2. Rolling under/ equal to the “focus” value of the same skill, which is basically a crit since it’s very rarely more than 3. In this way, it’s possible to roll up to 4 successes on only 2 dice, since you can have Focus 2 and roll a 1 and 2.   Now, the Roll20 sheet handles this all right by counting the Focus target as a “cs” and the Skill + Attribute as the main target.  Of course, this only turns the box green if you get the crit; you can only count the extra successes by mousing over.  This is fine for play but I really want to write some helper macros that count the full number of hits; there’s some cool stuff that can only be done with that number. I’ve seen this asked before and everyone said to go to API, but I’m not sure where to start.  I’m trying to make this work with PowerCards, which already has a few functions related to the CritCheck variable, but this could easily be its own script.  I’m stuck on the 2d20 roll needing to pull the Focus attribute; I can see how you would ring up extra successes for 1s but I don’t know how to make it work for a variable.   Would love any help - thanks!
1558104264
The Aaron
Roll20 Production Team
API Scripter
So, to summarize, you'd like a script that gets data from a roll (simplest example being passed an in-line roll, so I'll use that as the example), and calculates: • number of crits by die, rolling a 1 • number of crits by total, sum less than or equal to an attribute value And then reports that info.  So: !infdice [[2d20]] @{character_id} Might be the command, and if the rolls were 1,2 and the character's focus attribute was a 3, you get something like: 1+2=3, 4 crits Is that right?
Exactly, with the revision that Infinity doesn’t actually have crits in that way; I was just using a recognizable term to describe the Focus stuff.  My fault for saying “basically a crit,” hehe.  Every hit, from both Focus and Skill, is a regular success and should be tallied as such.  Thanks Aaron.
1558110204
The Aaron
Roll20 Production Team
API Scripter
So, if you want to write this, the basic layout would be: get a !infdice command Parse through the inline roll structure and count the number of 1s load the focus attribute for the specified character compare the value of the focus attribute to the sum output the results Probably 10-15 lines of code. If you don't want to try and write it or you just want a starting point for greater things, I can probably knock it out pretty quickly today.
Yes, that would be amazing.   I'm trying my best to teach myself but I'm just a complete noob here.
1558122255
The Aaron
Roll20 Production Team
API Scripter
Ok, here's a minimal implementation as a jumping off point.  I annotated the script to make it easier to understand when you go to make modifications.  Some of it is a wee bit complicated (the inline rolls), I can go over that in more detail if you find it daunting. Call it like this: !infdice [[2d20]] @{selected|character_id} or use attributes, @{target}, or the character ability shorthand notations. code: // wait till the ready event before registering a chat handler on('ready',()=>{ // register a function to run for chat messages on('chat:message',(msg)=>{ // make sure the message is an api command, then verify it starts with !infdice if('api'===msg.type && /^!infdice\b/i.test(msg.content)) { // break into an array of arguments on spaces let args=msg.content.split(/\s+/); // grab the characger in the 2nd parameter (0 will be !infdice, 1 will be the inline roll, 2 will be the character id) let c = getObj('character',args[2]); // verify we got a character if(c){ // get the characgter's focus attribute, or assume a focus of -1 let focus = parseInt((findObjs({ type: 'attribute', characterid: c.id, name: 'focus' })[0]||{get:()=>-1}).get('current')); // accumulators for hit, sum and the rolled dice let hits = 0; let sum = 0; let dice = []; // walk the inline roll stucture grabing the dice and checking if they are 1s (msg.inlinerolls||[]).forEach(r=>{ ((r.results||{}).rolls||[]).forEach(r2=>{ (r2.results||[]).forEach(r3=>{ hits += ((r3.v||0)===1?1:0); dice.push(r3.v||0); }); }); // and accumulate the sum sum += ((r.results||{}).total||0); }); // check the sum vs focus value and add 2 hits if equal or less if(sum<=focus){ hits+=2; } // output the rolls, sum, and hits sendChat('',`Rolls: ${dice.join('+')} = ${sum}: ${hits} hits.`); } else { // error for no matching character sendChat('',`No character found for id <code>${args[2]}</code>`); } } }); });
Thank you thank you, this is very helpful.  I can definitely work from here if I figure out one thing... I think I'm failing to call the focus attribute correctly, since I'm only able to get the hits on 1s in my testing.  Here's how the attribute is written out in the sheet html: type="text" name="attr_Close_Combat_Focus" value = "0"> And here's what I tried in the code: let focus = parseInt((findObjs({ type: 'attribute', characterid: c.id, name: 'Close_Combat_Focus' The character's Focus for this skill is 3, but I'm getting no extra hits.  Do you know what I'm missing?
1558189958

Edited 1558189983
The Aaron
Roll20 Production Team
API Scripter
That looks right. Try adding focus to the output and see if it's getting the right value: sendChat('',`Rolls: ${dice.join('+')} = ${sum}: ${hits} hits. [Focus: ${focus}]`);
Aaron, I'm sorry.  I realize why this isn't working - it's doing exactly what you designed it to do, but I failed to explain the 2d20 system properly.  I really kinda butchered it. I can actually still figure it out from here though with just one more bit of help.. I did some tinkering and got what I needed for a separate part of the roll function using this:      (msg.inlinerolls||[]).forEach(r=>{                     ((r.results||{}).rolls||[]).forEach(r2=>{                         (r2.results||[]).forEach(r3=>{                             hits += ((r3.v||0)===(skill+attr)?1:0);                             dice.push(r3.v||0);                         });                     }); This gives me hits for each die rolled equal to the attribute sum of "Skill+Attribute."  I actually want 1 hit for each die rolled that is equal to or LESS than the target number, but I don't know which characters to change in there; there's a lot of => going on  and I don't know what they apply to. (In this case, if (skill+attr) is 14, I would get 1 hit on a 2d20 roll of (13, 18), and 2 on (4, 14).) Almost there.  Thanks so much for your help.
1558226777

Edited 1558226813
GiGs
Pro
Sheet Author
API Scripter
Can you go back to basic, and describe in full how successes are calculated, in this format. I think  this is how it is worked out, but I'm not sure. Roll 2d20 Compare both dice against a target number (skill + attribute), and each that is less than or equal to target, add 1 hit Compare both dice against focus, and each that is less than or equal to focus, add 1 hit
1558228836

Edited 1558228922
GiGs
Pro
Sheet Author
API Scripter
I googled infinity rpg, and it looks like the above is correct, but also that not all rolls have focus. So here's a tweaked version of Aaron's script to account for this Call it this way: !infdice [[2d20]] attribute skill focus you can use queries or stat references, like !infdice [[2d20]] @{selected|dex} @{selected|shooting} @{selected|focus} or !infdice [[2d20]] ?{attribute|0} ?{skill|0} ?{focus|0} You can omit focus, but cant omit either attribute or skill. !infdice [[2d20]] @{selected|dex} @{selected|shooting} here's the tweaked script. It has two sendchat output lines. The second is a quick example of using the default rolltemplate. Delete or edit either :) // !infdice [[2d20]] attribute skill focus // wait till the ready event before registering a chat handler on('ready', () => { // register a function to run for chat messages on('chat:message', (msg) => { // make sure the message is an api command, then verify it starts with !infdice if ('api' === msg.type && /^!infdice\b/i.test(msg.content)) { // break into an array of arguments on spaces const args = msg.content.split(/\s+/); const target = (args[2]*1 + args[3]*1) || 0; const focus = args[4]*1 || 0; // accumulators for hit, sum and the rolled dice let hits = 0; let dice = []; // walk the inline roll structure grabbing the dice and comparing vs target and focus (msg.inlinerolls || []).forEach(r => { ((r.results || {}).rolls || []).forEach(r2 => { (r2.results || []).forEach(r3 => { hits += ((r3.v || 0) <= target ? 1 : 0); hits += ((r3.v || 0) <= focus ? 1 : 0); dice.push(r3.v || 0); }); }); }); // output the rolls, and hits sendChat('', `Rolls: ${dice.join('+')} = ${hits} hits.`); sendChat('', `&{template:default} {{name=${msg.who}}} {{target=${target} }} {{dice=${dice.join(' ')} }} {{hits=${hits}}}`); } }); });
Hot damn, that did it.  Thanks so much, both of you.  This is so helpful. Just a couple questions for my future understanding: What does the dice.push function do?  I tried looking this up and can find no mention on the API Wiki. And then, why is it that you need to double-nest the r.results with r.2 and r.3?  Can't quite figure out what's going on there. I'm excited.  Now I can make a lot of breakthroughs on this campaign!
1558277049

Edited 1558277244
The Aaron
Roll20 Production Team
API Scripter
Dice is an array, .push() appends a value to the array, so: let dice = [1,2,3]; dice.push(4); log(dice); // [1,2,3,4] .push() pushes a value on the end, .pop() removes and returns a value from the end.  These let you treat an array like a stack (FILO -- First, In Last Out). There are also .shift() to remove and return a value from the front, and .unshift() to put a value on the front.  Using .push() and .shift() let you treat an array like a queue (FIFO -- First In, First Out) The inlinerolls structure is deeply nested with a lot of information. Here's what it looks like for rolling 2d20: "inlinerolls": [ { "expression": "2d20", "results": { "resultType": "sum", "rolls": [ { "dice": 2, "results": [ { "v": 12 }, { "v": 3 } ], "sides": 20, "type": "R" } ], "total": 15, "type": "V" }, "rollid": "-LfFW3U3FfZ425Ol8XIt", "signature": "61f04c6fd1d47371aaa49b6700b7e8ee67b8927d23ec47b8d50e007e40a94c35ff0099b4f35a13f6adabd44eca49df59ab70ddacac2fd73939ea6724b46ebf89" } ], This block of code: (msg.inlinerolls || []).forEach(r => { ((r.results || {}).rolls || []).forEach(r2 => { (r2.results || []).forEach(r3 => { hits += ((r3.v || 0) <= target ? 1 : 0); hits += ((r3.v || 0) <= focus ? 1 : 0); dice.push(r3.v || 0); }); }); }); makes use of the interesting property of javascript whereby the  ||  (OR operator) returns the first "truthy" value, rather than a boolean.  Everywhere you see something like  || []  that is checking to make sure the previous thing exists and providing a default if it doesn't. So  (msg.inlinerolls || []).forEach(  will iterate over the  msg.inlinerolls  array, or will iterate over an empty array provided by  || [] . The first .forEach() iterates over each inline roll provided to the message, [[2d20]] [[3d6]] would result in two iterations. The second .forEach() iterates over the individual dice expressions in that inline roll, [[2d6+3d4+1d8]] would result in three iterations. The third .forEach() iterates over the individual dice rolls, [[7d8]] would result in seven iterations. Hope that helps!
1558277593
The Aaron
Roll20 Production Team
API Scripter
One other thing, the "Fat Arrow" functions created with =>: m => m+3; is (mostly) equivalent to: function(m){ return m+3; } They are a way to make more concise and easier to understand (once you are comfortable with them) blocks of code.  They also have some performance gains (minor) and can help solve some problems with scoping (this variables) that plague people in some instances (if you ever seen code that with "var that = this;" in it, you've seen the problem).
Ohhh, gotcha, so you basically have to remind Java all the stuff it's checking for on the dice? Interesting, thanks! If I may take this one a step further... I am now trying to nest the roll scripts for each skill in drop-down query macros, organized by base attribute.  Trying to save space but still have accessible buttons for every skill. For example, #Agility is: ?{Skill|Acrobatics, %{General|Acrobatics} |Stealth, %{General|Stealth} } With all the abilities hosted on a "General" character that any player has access to.  This works just fine with the script you wrote until I put any other queries in the inline roll.  For example, Infinity frequently has you specify the number of dice you're rolling, rather than the base 2.  So I start with: !infdice [[?{Dice|2}d20]] @{selected|Agility} @{selected|Acrobatics_Expertise} @{selected|Acrobatics_Focus} Which goes through the script, no problem.  But with a nested query, I have to do character replacements or it wigs out.  So I tried: !infdice [[?{Dice|2}d20]] @{selected|Agility} @{selected|Acrobatics_Expertise} @{selected|Acrobatics_Focus} And there are no more syntax errors, but the script doesn't fire.  I get: !infdice 26 9 5 3 With the roll of course being variable.  I'm guessing this is because it handles the d20 roll before outputting the script line into chat.  Is there any way around this, or do I have a dead end here?  Thanks!
1558285852
GiGs
Pro
Sheet Author
API Scripter
The standard advice these days for situations like this, is to avoid nesting queries, and use a chat menu instead. they work great with scripts. You could do something like the below (the linebreaks are for clarity - remove them before using the macro): /w gm &{template:default} {{name=Skills Menu}} {{ [Acrobatics]( ! infdice [[?{Dice|2}d20]] @{selected|Agility} @{selected|Acrobatics_Expertise} @{selected|Acrobatics_Focus}) [Shooting]( ! infdice [[?{Dice|2}d20]] @{selected|Agility} @{selected|Shooting_Expertise} @{selected|Shooting_Focus}) [Fast Talk]( ! infdice [[?{Dice|2}d20]] @{selected|Charisma} @{selected|FastTalk_Expertise} @{selected|FastTalk_Focus}) }}
I guess we had a very similar conversation a couple days ago, hehe.  Yeah, that can work too.  But... it's not fetching any values or rolling any dice for that chat menu.  On rolling I get an empty template that hasn't pulled any dice or stats.  And oddly enough it asks for the Dice query before any buttons are pressed?  
1558291543

Edited 1558291786
GiGs
Pro
Sheet Author
API Scripter
Dammit, the cursed html replacement comes up again. One way to fix this is create a universal macro, available to all players, with the text [[?{Dice|2}d20]] Lets say you call it dice , so you'd normally call it with #dice . But you can't use the # , you need to replace it with # So the macro call would look like /w gm &{template:default} {{name=Skills Menu}} {{ [Acrobatics](!infdice #dice @{selected|Agility} @{selected|Acrobatics_Expertise} @{selected|Acrobatics_Focus}) [Shooting](!infdice #dice @{selected|Agility} @{selected|Shooting_Expertise} @{selected|Shooting_Focus}) [Fast Talk](!infdice #dice @{selected|Charisma} @{selected|FastTalk_Expertise} @{selected|FastTalk_Focus}) }} Again, remove line breaks. The reason this works: roll20 tries to roll the dice immediately in the chat menu macro. You block that by using a html replacement to hide the fcat that it is a roll. On the first pass, roll20 now sees that as just text, and pastes it directly into chat. And when it pastes it in chat, the parser jumps into actioon and replaces the html entity with the #, so it becomes an inline roll, ready to work when you click the button.
1558292364
GiGs
Pro
Sheet Author
API Scripter
hmmm looks like that wont work either. Do you really need a variable number of dice there? lol It might be better to just pass the number of dice and do the dice rolls within the script
1558294632

Edited 1558294769
GiGs
Pro
Sheet Author
API Scripter
Got it (have I ever said how much i hate html entities in roll20 parsing, hehe): /w gm &{template:default} {{name=Skills Menu}} {{[Acrobatics](!infdice [[?{Dice|2}d20]] @{selected|Agility} @{selected|Acrobatics_Expertise} @{selected|Acrobatics_Focus}) [Shooting](!infdice [[?{Dice|2}d20]] @{selected|Agility} @{selected|Shooting_Expertise} @{selected|Shooting_Focus}) [Fast Talk](!infdice ?{Dice|2}d20]] @{selected|Charisma} @{selected|FastTalk_Expertise} @{selected|FastTalk_Focus})}} Replace the dice query with: [[?{Dice|2}d20]] Those closing ]] brackets and the ? to start the query were the problem here. How ironic, having to use so many html entities to avoid using html entities in a nested macro... (Still simpler than a nested dropdown macro, i believe.) Remember, if you store this in a universal macro, store a copy because if you re-open it, the entities will vanish It's better to store it in a macros character sheet , and make it controllable by all so pcs can use the macro.
Bingo.  You're absolutely the man.  (Or woman, unsure).  Many thanks ^_^
Going back again while this is relevant - how can I additionally grab the text of the skill name with this same script?  Trying to arrange the templates nicely.  I'm guessing this is an easy one, just lost as usual.  I see that I'd need to label that value at the beginning of the script.  If I use the format we've used so far, i.e.: const args = msg.content.split(/\s+/); const skill=  args[2]*1 || 0; That just returns 0 of course.  I guess the constants just grab number values? Ideally I'd be putting in  !infdice [[?{Dice|2}d20]] Acrobatics @{selected|Agility} @{selected|Acrobatics_Expertise} @{selected|Acrobatics_Focus} . With "Acrobatics" as the skill constant.
1558334915
GiGs
Pro
Sheet Author
API Scripter
You're correct that the operation below grabs number values: const skill=  args[2]*1 || 0; That includes error checking to make sure the input value is a number. That's important when passing attribute values that are meant to be numbers. You can crash the sandbox if you don't check numbers actually are numbers. If you are passing text its much simpler: const skill=  args[2]; That's it. Warning: make sure your skills don't have spaces in the name. The way this script is setup, you'll get the wrong values for target and focus if skill has spaces in the name. So to get that skill label, change this // break into an array of arguments on spaces const args = msg.content.split(/\s+/); const target = (args[2]*1 + args[3]*1) || 0; const focus = args[4]*1 || 0; to // break into an array of arguments on spaces const args = msg.content.split(/\s+/); const skill = args[2]; const target = (args[3]*1 + args[4]*1) || 0; const focus = args[5]*1 || 0;
1558360503
The Aaron
Roll20 Production Team
API Scripter
A lot of times I'll put a free form text argument last and just join what ever is left of the arguments with spaces. 
1558367854

Edited 1558368127
Perfect, got it.  Thank you!  Next step: Infinity has crit fails, or "complications" resulting usually on rolls of 20.  I already figured out how to add this result no sweat like so:  let cf = 0; and later cf += ((r3.v||0)===20?1:0); Beneath the inlinerolls thing.  BUT in Infinity, you're supposed to consider any skill with 0 Expertise (from argument 3) to be "untrained," and therefore it gets crit fails on 19 AND 20.  Seems like a fairly simple conditional idea (if Arg[3] = 0, then cf += ((r3.v||0)===19?1:0);) but my noobyness prevents me from knowing just how to write that.
1558381460

Edited 1558381692
GiGs
Pro
Sheet Author
API Scripter
You are so close. One way to do that would be to just add this if (args[3] == 0) cf += ((r3.v||0)===19 ? 1 : 0 ); However, its a good idea to check that arg[3] is a number, and it looks a bit not-neat if you are assigning args to variables earlier. So for neat-neas I'd rewrite the earlier bit from this: const args = msg.content.split(/\s+/); const target = (args[2]*1 + args[3]*1) || 0; const focus = args[4]*1 || 0; To this const args = msg.content.split(/\s+/); const stat = args[2]*1 || 0;             const skill = args[3]*1) || 0;             const target = stat + skill; const focus = args[4]*1 || 0; Then you can do that if statement, which is a bit more readable. if (skill === 0) cf += ((r3.v||0)===19 ? 1 : 0 ); Though that depends if you understand the ternary operator, which is basically (if something ? do this : otherwise do that ) If you wanted to get a little bit fancier, you could do it all on this line cf += ((r3.v||0)===20?1:0); First, you could change to 20 to a variable. let fumble = 20; if(skill ===0) fumble = 19; You could combine them on the same line let fumble = (skill === 0) ? 19: 20; This is doing the same thing, saying: if skill = 0, fumble = 19, otherwise fumble = 20 Then you have one line checking for fumbles: cf += ((r3.v||0)>= fumble ? 1 : 0); And if you wanted to be even more terse, you could combine all of that into a single line, by nesting the fumble statement: cf += ((r3.v||0)>= (skill === 0 ? 19: 20) ? 1 : 0); There's no right way to do it. When still learning the ropes, I recommend being as verbose as possible: make everything a named variable and work with them, and just do one thing at a time. But I wanted to show some different approaches, because often it helps to understand.
Very cool - got that in! Thanks.   You've got a pretty good track record with this stuff, eh? 
1558444443

Edited 1558449733
Update to this, plus another couple of questions.  First an explanation of what I'm accomplishing... For every skill test, Infinity has the GM set a difficulty between 0 and 5, with 1 being by far the most common.  It's just a target number of successes on the 2d20 roll.  I wanted to do some stuff with that so I added ?{Difficulty|1} as another Argument. Then one of my main goals with this script: Momentum.  Infinity's most important resource, Momentum, comes from any extra hits beyond the difficulty of a skill test.  So if you roll 3 hits on Difficulty 2, you get 1 Momentum.  4 hits versus Difficulty 1 = 3 Momentum.  This can either be spent right away on all sorts of heroic bonuses, or it can be saved in a group pool.  I wanted to track all this, and I succeeded!  The new script outputs Momentum and even updates the General momentum pool with the new bonus. // !infdice [[2d20]] attribute skill focus // wait till the ready event before registering a chat handler on('ready', () => {     // register a function to run for chat messages     on('chat:message', (msg) => {         // make sure the message is an api command, then verify it starts with !infdice          if ('api' === msg.type && /^!infdice\b/i.test(msg.content)) {                      // break into an array of arguments on spaces             const args = msg.content.split(/\s+/);             const skill = args[2];             const name = args [3];             const attribute = args[4]*1 || 0;             const expertise = args[5]*1 || 0;             const target = attribute + expertise;             const focus = args[6]*1 || 0;             const difficulty = args [7];             // accumulators for hit, dice, and complications or cf             let hits = 0;             let dice = [];             let cf = 0;                                       // walk the inline roll structure grabbing the dice and comparing vs target, focus and expertise             (msg.inlinerolls || []).forEach(r => {                 ((r.results || {}).rolls || []).forEach(r2 => {                     (r2.results || []).forEach(r3 => {                         hits += ((r3.v || 0) <= target ? 1 : 0);                         hits += ((r3.v || 0) <= focus ? 1 : 0);                         cf += ((r3.v||0)>= (expertise === 0 ? 19: 20) ? 1 : 0);                         dice.push(r3.v || 0);                     });                 });             });                          //generate momentum if successful enough             if (hits < difficulty) {                 momentum = 0;             } else momentum = hits - difficulty             // output the rolls, hits, fumbles and momentum in a powercard, then update the group momentum pool             sendChat('', `!power {{ --name| ${skill} TEST --leftsub| ${name} --SKILL:| D${difficulty} | Target of ${target} | Focus ${focus} --DICE:| ${dice.join(' ')} --RESULTS:| Hit ${hits} | CF ${cf} | Momentum ${momentum} }}`);             sendChat('', `!modattr --name General --Resolve|${momentum} --silent`);         }     }); }); So that's cool.  On to questions: Unfortunately now the script is crashing a lot, I guess?  I can really only run it once or twice before it stops responding.  There's no error message in the game or script sandbox, but I just click my buttons, input the query stuff, then wait forever.  Am I... just doing too many things now?  Is there a way to clean this up or otherwise mitigate the lag? Enemies in the game do not use the friendly Momentum of course, but they use an extremely similar resource called Heat.  It's just a GM resource rather than a player one.  It seems like I should be able to check that the character is an enemy, then have the script track Heat instead of Momentum, but I'm not sure where to start with that.  Also afraid that could compound my lag problem, so I'm nervous to start it. Thanks!
1558464658

Edited 1558464750
GiGs
Pro
Sheet Author
API Scripter
If the script is crashing a lot, the first thing I'd check is the values you are sending it. One other possibility: You have  const difficulty = args [7]; so if you ever run the script will less than 8 parameters, it will crash. Also since difficulty is a number, you should coerce it to a number like const difficulty = args [7]*1 || 0; If the script is treating it as text, it will also crash when you perform arithmetic on it later. You also dont declare momentum anywhere (using let,  var  or const  - only use const if the value never changes). A minor syntax issue (not an error, just stylistic):             if (hits < difficulty) {                 momentum = 0;             } else momentum = hits - difficulty It's better to use the same syntax for both branches of the if statement. So one of these             if (hits < difficulty)                 momentum = 0;             else                 momentum = hits - difficulty             if (hits < difficulty) {                 momentum = 0;             } else {                 momentum = hits - difficulty             }             if (hits < difficulty) momentum = 0;             else momentum = hits - difficulty You must  use the curly brackets { } version when you have multiple things going on in the if statement. If you are only doing a single thing on each line you dont need it, but you can also usually replace those with the ternary operator, like so (and combine with declaring the variable)             let momentum = (hits < difficulty) ? 0 : hits - difficulty; That looks confusing, but its the same thing as all the if statements above. Since you have momentum = something in both branches of if the iff statement, you start with    momentum =  and move the if statement after it. This is what its doing:             let variable = (check something) ? value if true : value if false; I'd also remove the power and modattr script calls when testing. Eliminate other potential sources of error, till you have the script working properly. Hope this helps!
1558474467

Edited 1558486849
I realized the difficulty argument mistake right after leaving for work.  Kicked myself for that all day, lol. Unfortunately neither that nor your helpful ternary fix fully solved it.  I did experiment with the scripts as suggested and it seems that is where the trouble lies; if I remove both it outputs no problem.  I tried reordering them but that gives me about the same problem.  Is it possibly due to me firing 2 scripts simultaneously before mine is fully resolved?   I turned off the chattattr because that's the hardest to work with right now, but this code still causes a lot of lag.  Lots of times I think it's a crash but it's really just taking 20 seconds to roll...  I'd say it's just my Internet but I have a pretty good connection for gaming most of the time.   Oof, nope, another few minutes of testing and this keeps on crashing.  Dang! // !infdice [[?{Dice|2}d20]] skillname charactername attribute expertise focus ?{Difficulty|1} // wait till the ready event before registering a chat handler on('ready', () => {     // register a function to run for chat messages     on('chat:message', (msg) => {         // make sure the message is an api command, then verify it starts with !infdice          if ('api' === msg.type && /^!infdice\b/i.test(msg.content)) {                      // break into an array of arguments on spaces             const args = msg.content.split(/\s+/);             const skill = args[2];             const name = args[3];             const attribute = args[4]*1 || 0;             const expertise = args[5]*1 || 0;             const target = attribute + expertise;             const focus = args[6]*1 || 0;             const difficulty = args[7]*1 || 0;             // accumulators for hit, dice, and complications or cf             let hits = 0;             let dice = [];             let cf = 0;                                       // walk the inline roll structure grabbing the dice and comparing vs target, focus and expertise             (msg.inlinerolls || []).forEach(r => {                 ((r.results || {}).rolls || []).forEach(r2 => {                     (r2.results || []).forEach(r3 => {                         hits += ((r3.v || 0) <= target ? 1 : 0);                         hits += ((r3.v || 0) <= focus ? 1 : 0);                         cf += ((r3.v||0)>= (expertise === 0 ? 19: 20) ? 1 : 0);                         dice.push(r3.v || 0);                     });                 });             });                          //generate momentum if successful enough             let momentum = (hits < difficulty) ? 0 : hits - difficulty;             // output the rolls, hits, fumbles and momentum in a powercard             sendChat('', `!power {{ --name| ${skill} TEST --leftsub| ${name} --SKILL:| D${difficulty} | Target of ${target} | Focus ${focus} --DICE:| ${dice.join(' ')} --RESULTS:| Hit ${hits} | CF ${cf} | Momentum ${momentum} }}`);         }     }); });
Hi GiGs, I think you have a typo.  There's a spurious end parenthesis.  const skill = args[3]*1) || 0; Personally, I'm not a fan of  codes like this: cf += ((r3.v||0)===20?1:0); Saving a few bytes to get a little fancy and a lot less readable.
Looks like that typo is not in the current script, so it wouldn't explain the crash : (
1558496755
GiGs
Pro
Sheet Author
API Scripter
aisforanagrams said: Hi GiGs, I think you have a typo.  There's a spurious end parenthesis.  const skill = args[3]*1) || 0; Personally, I'm not a fan of  codes like this: cf += ((r3.v||0)===20?1:0); Saving a few bytes to get a little fancy and a lot less readable. Regarding the typo: well spotted. I guess that was left over from when it was two variables being added together. Regarding the code, in general I agree with you, and I'm usually saying the same thing. I do prefer to be more verbose myself, but in that specific function, the r3.v part makes sense and is actually self-documenting. Part of the issue is that the .v is not avoidable, because its part of the way roll20 set up the msg.inlineRolls object. And r, r2, r3 are simply iterators (like the i in for (var i = 0; i < x; i++)  loops). Using different names for them wouldnt make the code any more readable. The rest of the line is the ternary operator I described above. I agree it does look very arcane and unreadable when you first see it, but when you grasp it, you'll use it a LOT, to replace more clunky if/then statements that take 5+ lines just assign a value. It's not just to save a few bytes, it's to save on typing too. :)
DISCLAIMER: I do not have access to the API and I haven't done js programming in years. Ryan B. said: Looks like that typo is not in the current script, so it wouldn't explain the crash : ( Ryan, I noticed that you are using 3 nested forEach - and iirc forEach comes with a significant performance hit (sometimes forEach is useful and clean solution, like when you need a closure per loop).  You might want to test an alternate code by converting those forEach with for loops and see if that helps at all.
1558501600

Edited 1558501628
GiGs
Pro
Sheet Author
API Scripter
That won't be an issue. There's no real difference in performance in modern javascript. Timestamped javascript developer link.
I'll defer to my disclaimer.  But, that's good to know.  I probably should have googled first before guessing at something with outdated info.  Haven't touched JS since my freelance days hacking together node.js apps.
1558504712
GiGs
Pro
Sheet Author
API Scripter
aisforanagrams said: I'll defer to my disclaimer.  But, that's good to know.  I probably should have googled first before guessing at something with outdated info.  Haven't touched JS since my freelance days hacking together node.js apps. It's an understandable mistake. Google is pretty inconsistent on this. There are a LOT of outdated articles that point out old differences. I'm pretty sure if there was a serious issue, our very own Scriptomancer, Aaron, wouldnt be using it :)
I guess I have to give up on using PowerCards for now?  Shame because I really like the relatively simple handling of conditional lines.  Correct me if I’m wrong, but it seems like my options for a nice-looking template print with this information as well damage rolls and such are: 1. Construct a custom roll template that can handle specific conditionals like momentum (not sure how) 2. Make several conditional sendChat outputs, checking whether it’s an attack roll, whether it hits, whether it does burst damage, etc., and produce a slightly different default template for each possibility? I definitely think I want something more flexible than the default template, but it seems like a lot of work for the first option... what do you guys think will work better for creating a full attack + damage template for this specialty system?
1558560221
GiGs
Pro
Sheet Author
API Scripter
If the sheet you are using has a rolltemplate, you can use that one. You could also build the html formatting within your script (which needs a little html/css knowledge).  I dont follow #2: doesnt your script calculate all the needed info in a roll? You can just put that info in a rolltemplate and print it out. You just include the bits you need. Ordinarily I wouldnt use power cards or chatsetattr in a script like this. You are in a script already, you can do what they do directly, within the script. Maybe looking at the power cards script and copying its html, and putting it into your script would be do-able, if you dont have a good rolltemplate to use. If you're happy with the look of the default rolltemplate, you can easily  do the conditional part within your script. I don't use power cards, so i cant translate what your previous output does, but building on the default template i had before, here's one way to make it conditional: let output = `&{template:default} {{name=${msg.who}}} {{target=${target} }} {{dice=${dice.join(' ')} }} {{hits=${hits}}}`; if(momentum > 0) {     output += {{Momentum=${momentum} }}; } sendChat('', output); You just need to alter that to fit your desired fields.
1558586564

Edited 1558586599
OK, that's very good to know - I didn't think I could manipulate the chat output like that. Yeah, Infinity doesn't have its own template.  I'm in part doing all this to help future Infinity players, hehe. I'm already using a custom sheet so adding templates is no problem.  I was originally running the macros through the 5e Attack + Damage template because I love the format, but I realized it has some awkward conditional behaviors specific to DnD.  There's a field you can only access with a variable on that sheet, and another that tacks on a plus sign to the damage field if you roll a 20, I think?   But I do love subheaders and colors and specialized format stuff so I would really prefer something other than the default template.  This would cause a conflict with the output modifier above, because you'd always have to have something in those pre-defined fields, right? I brainstormed what I think might be a workaround for this - can anyone confirm that it would work before I waste time programming it? Goal is for a custom template to print Damage, Location, and Effect fields  only  if Hits >= Difficulty. If I understand correctly, roll template logic only works by processing inline rolls that you submit to it?  So if you want to use a constant, you have to paste in a d0? In my script output, I could send Hits and Location to their own fields with [[d0 + ${hits}]] (and nothing else in the field, if necessary?) In the template HTML, I then put all of the success-conditional fields within a {{#^rollLess() hits difficulty}} function so that they only show up if the hits are NOT less than difficulty. (Gotta thank you again for writing about this trick in a reply a year ago, GiGs.)
1558596157
GiGs
Pro
Sheet Author
API Scripter
I did have a longer reply submitted but thanks to the current forum logging out bug, I lost it. In short: rolltemplates recognise numbers only if they result from a roll. That means you just need to put [[ ]] around a number to get it recognised, you dont need to add d0. In a rolltemplate, you can have some sections set to display conditionally. Check out the first sentence under the Logic paragraph on the wiki page for rolltemplates. You could for xeample have the momentum section set to display, only if a {{momentum= }} entry exists in the text you send from the script.  So, you can do the if statement in your script ( if (hits ?= difficulty) output += {{damage= etc}} ) and then your rolltemplate set up only to show damage, location, and effects if a {[damage=}} section exists. So you can do it either way. The way you suggest above, or the way i illustrate here. But to answer the general question: yes it is possible.  *prays I dont get logged out when i click submit...*
1558620280

Edited 1558621599
Wait... I really screwed something up.  Custom templating is giving me trouble so I'm trying to mess around with the default, but I ended up with a syntax error around the output conditional.  Can't quite suss it out, but it's definitely something to do with the brackets. // !infdice [[2d20]] attribute skill name attribute expertise focus difficulty // wait till the ready event before registering a chat handler on('ready', () => {     // register a function to run for chat messages     on('chat:message', (msg) => {         // make sure the message is an api command, then verify it starts with !infdice          if ('api' === msg.type && /^!infdice\b/i.test(msg.content)) {             // break into an array of arguments on spaces             const args = msg.content.split(/\s+/);             const skill = args[2];             const name = args[3];             const attribute = args[4]*1 || 0;             const expertise = args[5]*1 || 0;             const target = attribute + expertise;             const focus = args[6]*1 || 0;             const difficulty = args[7]*1 || 0;             // accumulators for hit, dice, and complications or cf             let hits = 0;             let dice = [];             let cf = 0;                                       // walk the inline roll structure grabbing the dice and comparing vs target, focus and expertise             (msg.inlinerolls || []).forEach(r => {                 ((r.results || {}).rolls || []).forEach(r2 => {                     (r2.results || []).forEach(r3 => {                         hits += ((r3.v || 0) <= target ? 1 : 0);                         hits += ((r3.v || 0) <= focus ? 1 : 0);                         cf += ((r3.v||0)>= (expertise === 0 ? 19: 20) ? 1 : 0);                         dice.push(r3.v || 0);                     });                 });             });                          //generate momentum/heat if successful enough             let momentum = (hits < difficulty) ? 0 : hits - difficulty;             let heat = (hits < difficulty) ? 0 : hits - difficulty;             let output = `&{template:default} {{name= ${skill} TEST }} ${name} {{SKILL= D${difficulty} | Target of ${target} | Focus ${focus} }} {{DICE= ${dice.join(' ')} }} {{RESULTS= Hit ${hits} | CF ${cf}}`;              if (momentum > 0) {              output += {{MOMENTUM=$momentum} }};              }                         // output the rolls, hits, fumbles and momentum             sendChat('', output);            }     }); });
Yeah, I think it doesn't want me to put the { anywhere inside the output += thing.  And I think I also have to put all that in parentheses? Meaning the error stops returning when I turn the statement into:      if (momentum > 0) {              output += (MOMENTUM=$momentum);          } But obviously I need those {{ to make the template and call variables.
Hi Ryan, The variable output is initialized as a template string in the line let output = `&{template:default} {{name= ${skill} TEST }} ${name} {{SKILL= D${difficulty} | Target of ${target} | Focus ${focus} }} {{DICE= ${dice.join(' ')} }} {{RESULTS= Hit ${hits} | CF ${cf}}`; If you are trying to append to that string, you want quotes around that.  Something like output += `{{MOMENTUM=${momentum}}}`;
^^ Thank you!  There is now no syntax error, but this output doesn't call the momentum value; it only prints "${momentum}."  How can I make it print the called variable?
Make sure you have the correct quotation.  Javascript engine will only recognize it as a template string if you put the single "backtick" quote.  If you do, the ${momentum} should be replaced by its value.  You can check this by console.log(output) and checking the javascript console to see you're actually building the string that you want. Alternately, if you don't want to bother with templates, you can just do something like output = output + '{{MOMENTUM=' + momentum + '}}'; Notice the different quotation mark.
1558716762

Edited 1558716777
Yes!!  Thanks, I keep missing that, lol.  I now have a script that delivers optional momentum outputs, or Heat if it's an enemy.  Now I'm going to start combining all of this with weapon attacks.  
I'm glad I could be of help!