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

API issues setting up for The One Ring 2nd Edition

1625240381

Edited 1625241271
The Company
Pro
Compendium Curator
I'm using the excellect APIs created by Chris Heilmann to get it up and running. The new system leans heavily on favoured and ill-favoured rolls (roll two Feat dice and drop the worst, or the best). The One Ring Dice Listener API does a great job of "hearing" special rolls on the Feat die (d12) when one is rolled (it can see and report a "Gandalf" "Eye" and "Tengwar" result just fine). However, when we roll 2xFeat and keep one Feat die result ({2t[feat]}k1), it fails to "see" the results, and they become undefined and it all breaks down. This is Chris' API . And I know it breaks down because it's just parsing one of the two d12 Feat Die rolls, but, for the life of me, I can't get it to parse both and keep the number reported (featResult) from being undefined. It's this bit specifically, I think: // loop through dice results if (roll.type === 'R') { if (roll.sides === 12) { featResult = roll.results[0].v; automatic = (roll.table === 'lm-feat' && roll.results[0].tableidx === 10 ? true : automatic); // eye as adversary automatic = (roll.table === 'feat' && roll.results[0].tableidx === 11 ? true : automatic); // gandalf as player eye = (roll.table === 'feat' && roll.results[0].tableidx === 10 ? true : eye); // eye as player } if (roll.sides === 6) { // check for tengwars roll.results.forEach(function(result) { tengwars = (result.v === 6 ? tengwars + 1 : tengwars); }, this); } } }, this); Anyone see an easy way to fix it? Cheers, -Dennis
1625246854
timmaugh
Forum Champion
API Scripter
Currently on my phone, so I can't give you a proof of concept, but... He *is* checking all rolls. He has a forEach() iterating over the inline rolls (one level out from what you excerpted). It provides the roll (the iterated object). Later, he refers to the results[0] of that roll, but that's just where you have to look within each roll. The problem I think is that it is setting a script-scoped variable. You're always going to end up with the results of the last one. I would track the iterated results of the above code (for instance, storing an array of values that would otherwise be assigned to automatic), and you will see that they are all handled at one point. Once the array has been collected and you're done walking the inline rolls, you can get the value out of the array based on better/worse value. You can grab it by a sort, by position index, or by a reduce function that returns only the one value you want after looking at them all. If you want an example of that, I can provide one later, when I'm back in front of my PC.
1625248155
The Company
Pro
Compendium Curator
I would love that, Tim, and it would be very helpful. Parsing rolls is proving somewhat beyond me. -D
1625285543

Edited 1625605598
timmaugh
Forum Champion
API Scripter
OK, when I actually got in to look at this, it wasn't the script scoped variable at all. Those are just looking for any value matching a parameter and returning true/false accordingly. That will work fine. The problem was that there was a roll type that was not being accounted for... Type G (Group). The script was testing for C and R types, but not G. Once you roll against the same table twice (ie, 2t[feat]), the roll values get nested pretty deeply. The solution was to obviously add a test for type === 'G' , but also to move the whole testing under its own function. This is because for type G rolls, you have sub-rolls. For each of these you have to call the same procedure recursively to drill down to the type C or type R rolls, and you can't do that with a forEach callback. Important parts: Line 101 (below) kicks over to the new function Line 90 detects the type G roll, and iterates over the sub rolls; there are 2 forEach loops here because the G rolls are an array... of arrays... of roll objects. So we have to get to the rolls by iterating the first array, then the second. You can see this for yourself if you un-remark line 50 and send a basic roll through like /roll {2t[lm-feat]}kh1 . Then we recursively call the same function (line 94), and see if we catch G, R, or C types. This worked in the very small mock up I did of the game/script, but my mock up wasn't complete so there might be edge cases and/or broken functionalities. Post back if you have any trouble. In the meantime, try this: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 /* The One Ring Dice Checker for Roll20. By Michael Heilemann (<a href="mailto:michael.heilemann@me.com" rel="nofollow">michael.heilemann@me.com</a>) Updated by uhu79 This is an API script for Roll20.net, which checks rolls against the success criteria of the The One Ring system, and is best used in conjunction with the custom dice roll tables (<a href="https://wiki.roll20.net/The_One_Ring" rel="nofollow">https://wiki.roll20.net/The_One_Ring</a>). Basically it will try to output a valuable message about the roll's success, and works best of supplied with a Target Number, like so: /roll 1d12 + 3d6 &gt; 14 Or as would be the case if TOR roll tables are set up properly: /roll 1t[feat] + 3t[normal] &gt; 14 Or for the enemy: /roll 1t[lm-feat] + 3t[normal] &gt; 14 It know about Gandalf's Rune, and the great lidless eye, wreathed in flame, and counts them as success and failure respectively (based on whether you use the feat or lm-feat tables to roll from). It also checks if your are using 'speaking as' and displays the name accordingly. Update: it also checks if you have rolled EDGE on your feat-die with the attack roll this only works if your macros are following a clear structure and your roll commands for rolls look like this: /r 1t[feat] + @{selected|weapon_rating_1}t[@{selected|Weary}] &gt; [[?{modifier|0} +@{selected|stance} + @{target|parry} + @{target|shield}]] where after "&gt;" you have the target number for this roll and in case of an attack the roll should look like this: /r 1t[feat] + @{selected|weapon_rating_1}t[@{selected|Weary}] &gt; [[?{modifier|0} +@{selected|stance} + @{target|parry} + @{target|shield}]] Edge: @{selected|weapon_edge_1} where at the end you put the edge-attribute of the weapon Aditionally, this script now also checks if a player rolled an eye on a missed attack. A chat msg is sent accordingly, stating that the opponent might try a called shot next. */ on( 'chat:message' , function (e) { if (e.type === 'rollresult' ) { var content = JSON.parse(e.content); var rolls = content.rolls; var tn = false ; var edge = false ; var automatic = false ; var eye = false ; var tengwars = 0 ; var featResult; var piercing = "" ; var eyeOnAttack = "" ; // log(JSON.stringify(content, undefined,2)); // determine who triggered the roll, player or character var characters = findObjs({ _type: 'character' }); var speaking; characters.forEach( function (chr) { if (chr.get( 'name' ) == e.who) speaking = chr; }); const parseRoll = roll =&gt; { // detect Target Number if (roll.type === 'C' ) { var text = roll.text.replace( /\s/g , '' ); // remove whitespace //split the string into an array separated by space var params = roll.text.splitArgs(); //log(params); //the target number is found at position 1 of the array (see the macro) tn = params[ 1 ]; //the edge value is found at position 4 of the array (see the macro) //a params array for attack-rolls should look like this ["&gt;", "(tn)", "Edge:", "(edge)"] //if this is not an attack-roll then the variable edge will be undefined edge = params[ 3 ]; } // loop through dice results if (roll.type === 'R' ) { if (roll.sides === 12 ) { roll.results.forEach(res =&gt; { featResult = typeof featResult === 'undefined' ? res.v : featResult; automatic = (roll.table === 'lm-feat' &amp;&amp; res.tableidx === 10 ? true : automatic); // eye as adversary automatic = (roll.table === 'feat' &amp;&amp; res.tableidx === 11 ? true : automatic); // gandalf as player eye = (roll.table === 'feat' &amp;&amp; res.tableidx === 10 ? true : eye); // eye as player }); } if (roll.sides === 6 ) { // check for tengwars roll.results.forEach( function (result) { tengwars = (result.v === 6 ? tengwars + 1 : tengwars); }, this ); } } if (roll.type === 'G' ) { featResult = typeof featResult === 'undefined' ? roll.results[ 0 ].v : featResult; roll.rolls.forEach(r =&gt; { r.forEach(r1 =&gt; { parseRoll(r1); }); }); } }; rolls.forEach( function (roll) { parseRoll(roll); }, this ); //set chat msgs //if this is an attack, then edge is set (see macro) if (edge) { if (featResult &gt;= edge || automatic) { piercing = " And might inflict a wound!" ; } //setting a chat-msg if player has rolled an eye during an attack if (eye) { eyeOnAttack = " And provokes the opponent to try a Called Shot next round!" ; } } /* //old version where the weapon-slot-nr was handed over via macro //this only worked with players though, so I changed it if (weapon) { //looking up the edge-attribute only works if a speaking as character if (speaking) { var edge = getAttrByName(speaking.id, 'weapon_edge_'+weapon, 'current'); if (featResult &gt;= edge || automatic) { piercing = " Und schlägt vielleicht eine Wunde!"; } } } */ // gandalf rune for feat table, or eye for lm-feat table if (automatic) { if (tengwars === 0 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls an automatic success!' + piercing); else sendChat( 'player|' + e.playerid, '/desc rolls an automatic success!' + piercing); } else if (tengwars === 1 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls an automatic great success!' + piercing); else sendChat( 'player|' + e.playerid, '/desc rolls an automatic great success!' + piercing); } else if (tengwars &gt; 1 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls an automatic extraordinary success!' + piercing); else sendChat( 'player|' + e.playerid, '/desc rolls an automatic extraordinary success!' + piercing); } // a hit } else if (tn !== false &amp;&amp; content.total &gt;= tn) { if (tengwars === 0 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls a success!' + piercing); else sendChat( 'player|' + e.playerid, '/desc rolls a success!' + piercing); } else if (tengwars === 1 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls a great success!' + piercing); else sendChat( 'player|' + e.playerid, '/desc rolls a great success!' + piercing); } else if (tengwars &gt; 1 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls an extraordinary success!' + piercing); else sendChat( 'player|' + e.playerid, '/desc rolls an extraordinary success!' + piercing); } // a miss } else if (tn !== false &amp;&amp; content.total &lt; tn) { if (speaking) sendChat( 'character|' + speaking.id, '/desc misses.' + eyeOnAttack); else sendChat( 'player|' + e.playerid, '/desc misses.' + eyeOnAttack); } else { if (tengwars === 1 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls a tengwar.' ); else sendChat( 'player|' + e.playerid, '/desc rolls a tengwar.' ); } else if (tengwars === 2 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls two tengwars.' ); else sendChat( 'player|' + e.playerid, '/desc rolls two tengwars.' ); } else if (tengwars &gt; 2 ) { if (speaking) sendChat( 'character|' + speaking.id, '/desc rolls whole lotta tengwars.' ); else sendChat( 'player|' + e.playerid, '/desc rolls whole lotta tengwars.' ); } } } }); /** * Splits a string into arguments using some separator. If no separator is * given, whitespace will be used. Most importantly, quotes in the original * string will allow you to group delimited tokens. Single and double quotes * can be nested one level. * * As a convenience, this function has been added to the String prototype, * letting you treat it like a function of the string object. * * Example: on('chat:message', function(msg) { var command, params; params = msg.content.splitArgs(); command = params.shift().substring(1); // msg.content: !command with parameters, "including 'with quotes'" // command: command // params: ["with", "parameters,", "including 'with quotes'"] }); */ var bshields = bshields || {}; bshields.splitArgs = ( function () { 'use strict' ; var version = 1.0 ; function splitArgs(input, separator) { var singleQuoteOpen = false , doubleQuoteOpen = false , tokenBuffer = [], ret = [], arr = input.split( '' ), element, i, matches; separator = separator || /\s/g ; for (i = 0 ; i &lt; arr.length; i++) { element = arr[i]; matches = element.match(separator); if (element === '\'' ) { if (!doubleQuoteOpen) { singleQuoteOpen = !singleQuoteOpen; continue ; } } else if (element === '"' ) { if (!singleQuoteOpen) { doubleQuoteOpen = !doubleQuoteOpen; continue ; } } if (!singleQuoteOpen &amp;&amp; !doubleQuoteOpen) { if (matches) { if (tokenBuffer &amp;&amp; tokenBuffer.length &gt; 0 ) { ret.push(tokenBuffer.join( '' )); tokenBuffer = []; } } else { tokenBuffer.push(element); } } else if (singleQuoteOpen || doubleQuoteOpen) { tokenBuffer.push(element); } } if (tokenBuffer &amp;&amp; tokenBuffer.length &gt; 0 ) { ret.push(tokenBuffer.join( '' )); } return ret; } return splitArgs; }()); String .prototype.splitArgs = String .prototype.splitArgs || function (separator) { return bshields.splitArgs( this , separator); };
1625311841
Oosh
Sheet Author
API Scripter
How did you post that code, Timbot? It broke my codeWrap CSS in twain :( But otherwise it looked excellent
1625314436
timmaugh
Forum Champion
API Scripter
Her hee! hilite.me It's a trick Aaron shared a couple of weeks ago.
1625319149
The Company
Pro
Compendium Curator
Tim, thanks so much. I never would have figured out the 'G' type. I really appreciate the time and thought you put into this. It works great, but the only situation I've found it NOT working in is a Gandalf in the second position of 2tkh1(see below) -- this should be an automatic success and a wound, but its not. Any thoughts? If not, thanks for all your effort and time. Cheers, Dennis
1625321423
timmaugh
Forum Champion
API Scripter
I would have to dig into this further, but I wonder if the k1 or kh1 is working against the Gandalf rune... since the value in the table for that rune is a 0? It still seems that the code would examine the values, though. Hmm. I will try to take a look later.&nbsp;
1625323290
GiGs
Pro
Sheet Author
API Scripter
timmaugh said: Her hee! hilite.me It's a trick Aaron shared a couple of weeks ago. I wasnt around then. Can you link that? It looks nifty.
1625336482

Edited 1625421654
timmaugh
Forum Champion
API Scripter
Sorry ... I thought it would auto highlight the link when I put it in the original message. It's in what you quoted from me: hilite.me Pick your format, then copy/paste the formatted section.
1625337733
GiGs
Pro
Sheet Author
API Scripter
Weirdly that link doesnt work if I click it, but works if I manually copy it and paste it. Thanks!
1625605449
timmaugh
Forum Champion
API Scripter
OK, Loremaster... I took a deeper look at it and I think I found the problem. Each 'R' type roll could have multiple results in it (for instance, 2 if you did a 2t[feat]), however the script was only testing the first one. I have updated the code in the post above to be fixed/current. I have changed it now to check every result within an 'R' type for the purposes of "automatic" and "eye" (which look to be something critical successes and failures for player rolls). The Gandalf rune coming up in the second spot of the two rolls against the table produced the correct outcome for me (automatic). What I'm not sure of is what the "featResult" should represent in the game (not familiar with the mechanics)... in the code, it is being compared to the Edge number, so I think that it should take the overall result of the roll against the table. In other words, if you say to return the highest roll (kh1), then it should get that. Same for keeping the lowest one. If there is more than one roll against the table (a 'G' roll), that is really just taking the result of the 'G' roll, because by the time the script parses the roll the correct high/low roll is stored in the roll result for the 'G' roll. The script should get the featResult from the 'G' roll if there is more than one roll against your initial table, or it will get the first (and only) result in an 'R' roll if there is only 1 roll against the table. If that is correct, then the code should work as is. If I'm misunderstanding the application of the mechanic in the game system, post back and I can update.
1625766953
The Company
Pro
Compendium Curator
Wow Tim. Above and beyond; thanks very much for your kind effort. I'll check it out! -D