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

RecursiveTable macro returns 0 randomly

1st post, so be gentle.  ;-) I'm using Aaron's fabulous RecursiveTables macro but many of my rollable tables are printing '0' for part of a result, but not every time.  I've added some debugging log messages to RecursiveTable's sendChatP function: sendChatP = function(msg){ log(msg); log('=' + msg.replace(/\[\[\s+/,'[[').replace(/\[\[\s+\[\[/,'[[[[')); return new Promise((resolve) =>{ sendChat('',msg.replace(/\[\[\s+/,'[[').replace(/\[\[\s+\[\[/,'[[[['),(res)=>{     log(res[0].type + ':' + res[0].inlinerolls.length + '==' + res[0].content);      for(var ii=0; ii<res[0].inlinerolls.length;ii++) { log(res[0].inlinerolls[ii]); } resolve(res[0]); }); }); }, Here is one of the rollable tables that shows the problem: !import-table --BagOfBeansEffects --hide !import-table-item --BagOfBeansEffects --<%%91%%><%%91%%>5d4<%%93%%><%%93%%> toadstools sprout. If a creature eats a toadstool, roll any die. On an odd roll, the eater must succeed on a DC 15 Constitution saving throw or take 5d6 poison damage and become poisoned for 1 hour. On an even roll, the eater gains 5d6 temporary hit points for 1 hour. --1 -- !import-table-item --BagOfBeansEffects --A geyser erupts and spouts <%%91%%><%%91%%>1t<%%91%%>GeyserEffects<%%93%%><%%93%%><%%93%%> 30 feet into the air for <%%91%%><%%91%%>1d12<%%93%%><%%93%%> rounds. --9 -- !import-table-item --BagOfBeansEffects --A treant sprouts. There's a 50 percent chance that the treant is chaotic evil and attacks. --10 -- !import-table-item --BagOfBeansEffects --An animate, immobile stone statue in your likeness rises. It makes verbal threats against you. If you leave it and others come near, it describes you as the most heinous of villains and directs the newcomers to find and attack you. If you are on the same plane of existence as the statue, it knows where you are. The statue becomes inanimate after 24 hours. --10 -- !import-table-item --BagOfBeansEffects --A campfire with blue flames springs forth and burns for 24 hours (or until it is extinguished). --10 -- !import-table-item --BagOfBeansEffects --<%%91%%><%%91%%>1d6+6<%%93%%><%%93%%> shriekers sprout --10 -- !import-table-item --BagOfBeansEffects --<%%91%%><%%91%%>1d4+8<%%93%%><%%93%%> bright pink toads crawl forth. Whenever a toad is touched, it transforms into a Large or smaller monster of the GM's choice. The monster remains for 1 minute, then disappears in a puff of bright pink smoke. --10 -- !import-table-item --BagOfBeansEffects --A hungry bulette burrows up and attacks. --10 -- !import-table-item --BagOfBeansEffects --A fruit tree grows. It has <%%91%%><%%91%%>1d10+20<%%93%%><%%93%%> fruit, including fruit that acts as these potions:<%%60%%>br/<%%62%%><%%91%%><%%91%%><%%91%%><%%91%%>1d8<%%93%%><%%93%%>t<%%91%%>Potions<%%93%%><%%93%%><%%93%%><%%60%%>br/<%%62%%><%%60%%>br/<%%62%%>and one acts as an ingested poison:<%%60%%>br/<%%62%%><%%91%%><%%91%%>1t<%%91%%>InjestedPoison<%%93%%><%%93%%><%%93%%><%%60%%>br/<%%62%%><%%60%%>br/<%%62%%>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days. --1000 -- !import-table-item --BagOfBeansEffects --A nest of <%%91%%><%%91%%>1d4+3<%%93%%><%%93%%> eggs springs up. Any creature that eats an egg must make a DC 20 Constitution saving throw. On a successful save, a creature permanently increases its lowest ability score by 1, randomly choosing among equally low scores. On a failed save, the creature takes 10d6 force damage from an internal magical explosion. --10 -- !import-table-item --BagOfBeansEffects --A pyramid with a 60-foot square base bursts upward. Inside is a sarcophagus containing a mummy lord. The pyramid is treated as the mummy lord's lair, and its sarcophagus contains treasure of the GM's choice. --9 -- !import-table-item --BagOfBeansEffects --A giant beanstalk sprouts, growing to a height of the GM's choice. The top leads where the GM chooses, such as to a great view, a cloud giant's castle, or a different plane of existence. --1 -- Specifically, the table-item that starts with 'A fruit tree grows' sometimes prints '0' for the result of the InjestedPoison table roll.  For testing, note that I have temporary set the with to 1000 so that it occurs most often.  Here is the referenced InjestedPoison table: !import-table --InjestedPoison --hide !import-table-item --InjestedPoison --Assassin's Blood --1 -- !import-table-item --InjestedPoison --Midnight Tears --1 -- !import-table-item --InjestedPoison --Pale Tincture --1 -- !import-table-item --InjestedPoison --Torpor --1 -- !import-table-item --InjestedPoison --Truth Serum --1 -- I run my tests from the chat window using this command: !rt[Delimiter:BR] /w gm &{template:default}{{Effect=[[1t[BagOfBeansEffects]]]}} And finally, here are 2 sample outputs of the log messages, one where InjestedPoison worked and another where it printed '0': Here is the result that worked, along with the log messages: Effect A fruit tree grows. It has 28 fruit, including fruit that acts as these potions: Potion of flying Potion of healing (superior) Potion of healing Potion of frost giant strength and one acts as an ingested poison: Pale Tincture The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days. "=A fruit tree grows. It has [[1d10+20]] fruit, including fruit that acts as these potions:<br/>[[[[1d8]]t[Potions]]]<br/><br/>and one acts as an ingested poison:<br/>[[1t[InjestedPoison]]]<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." "general:4==A fruit tree grows. It has $[[0]] fruit, including fruit that acts as these potions:<br/>$[[2]]<br/><br/>and one acts as an ingested poison:<br/>$[[3]]<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." {"expression":"1d10+20","results":{"type":"V","rolls":[{"type":"R","dice":1,"sides":10,"mods":{},"results":[{"v":8}]},{"type":"M","expr":"+20"}],"resultType":"sum","total":28},"signature":"a484aa4156d956416a447c4f2f49549b372cae7496618e003c6f80db1fcbd4819721d1183de17b454cded314e86e138261ee9563f2427b63ae4f971689ec1210","rollid":"-LLivE2aWhmuZn-mwqBJ"} {"expression":"1d8","results":{"type":"V","rolls":[{"type":"R","dice":1,"sides":8,"mods":{},"results":[{"v":4}]}],"resultType":"sum","total":4},"signature":"12575b2a3a300d45be14cfdda6f32f60c085197451d17d6327021f3f07354bd45f10fe86d60b6c7f59efa298a833ba8b0e10e140410f1c0fbdeb52f6aa64bcfb","rollid":"-LLivE2aWhmuZn-mwqBK"} {"expression":"4t[Potions]","results":{"type":"V","rolls":[{"type":"R","dice":4,"table":"Potions","mods":{},"sides":27,"results":[{"v":0,"tableidx":22,"tableItem":{"name":"Potion of flying","weight":1,"id":"-LLf25WSAdNuY-eSwuIX"}},{"v":0,"tableidx":9,"tableItem":{"name":"Potion of healing (superior)","weight":1,"id":"-LLf25WB3QVW2cRuxHHS"}},{"v":0,"tableidx":0,"tableItem":{"name":"Potion of healing","weight":1,"id":"-LLf25W-cyy5yqbz9e8A"}},{"v":0,"tableidx":13,"tableItem":{"name":"Potion of frost giant strength","weight":1,"id":"-LLf25WGuqo2p5d6jUbQ"}}]}],"resultType":"sum","total":0},"signature":false,"rollid":null} {"expression":"1t[InjestedPoison]","results":{"type":"V","rolls":[{"type":"R","dice":1,"table":"InjestedPoison","mods":{},"sides":5,"results":[{"v":0,"tableidx":2,"tableItem":{"name":"Pale Tincture","weight":1,"id":"-LLf25K7YSF25u37O9yW"}}]}],"resultType":"sum","total":0},"signature":false,"rollid":null} And here is the '0' result along with it's log output: Effect A fruit tree grows. It has 30 fruit, including fruit that acts as these potions: Potion of healing (supreme) Potion of cloud giant strength Potion of frost giant strength Potion of healing (supreme) Potion of climbing and one acts as an ingested poison: 0 The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days. "=A fruit tree grows. It has [[1d10+20]] fruit, including fruit that acts as these potions:<br/>[[[[1d8]]t[Potions]]]<br/><br/>and one acts as an ingested poison:<br/>[[1t[InjestedPoison]]]<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." "general:4==A fruit tree grows. It has $[[0]] fruit, including fruit that acts as these potions:<br/>$[[3]]<br/><br/>and one acts as an ingested poison:<br/>0<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." {"expression":"1d10+20","results":{"type":"V","rolls":[{"type":"R","dice":1,"sides":10,"mods":{},"results":[{"v":10}]},{"type":"M","expr":"+20"}],"resultType":"sum","total":30},"signature":"225a127514a179cf269ec370e08839b06161d7ea3602e891f4a64da26b014567411205347a9c9fb176711c0104c5278c0ab57a8be9da7700817c75d8433f049c","rollid":"-LLiqdeLqcVFsWJFXmgr"} {"expression":"1d8","results":{"type":"V","rolls":[{"type":"R","dice":1,"sides":8,"mods":{},"results":[{"v":5}]}],"resultType":"sum","total":5},"signature":"8e082ef7e4e85e905ab7450de208b3eabaf37b3e68ed07d593193e63a30b9487420a4edd827c54b3a0276e157366b0c5574b0f8449ef8507bd1499e2d6c51c8c","rollid":"-LLiqdePgqSIAmYFnS7N"} {"expression":"1t[InjestedPoison]","results":{"type":"V","rolls":[{"type":"R","dice":1,"table":"InjestedPoison","mods":{},"sides":5,"results":[{"v":0,"tableidx":4,"tableItem":{"name":"Truth Serum","weight":1,"id":"-LLf25KAYUoF61iaBqbE"}}]}],"resultType":"sum","total":0},"signature":false,"rollid":null} {"expression":"5t[Potions]","results":{"type":"V","rolls":[{"type":"R","dice":5,"table":"Potions","mods":{},"sides":27,"results":[{"v":0,"tableidx":19,"tableItem":{"name":"Potion of healing (supreme)","weight":1,"id":"-LLf25WOUsoNhmD1VRZZ"}},{"v":0,"tableidx":23,"tableItem":{"name":"Potion of cloud giant strength","weight":1,"id":"-LLf25WUWHpmNddh-7k-"}},{"v":0,"tableidx":13,"tableItem":{"name":"Potion of frost giant strength","weight":1,"id":"-LLf25WGuqo2p5d6jUbQ"}},{"v":0,"tableidx":19,"tableItem":{"name":"Potion of healing (supreme)","weight":1,"id":"-LLf25WOUsoNhmD1VRZZ"}},{"v":0,"tableidx":1,"tableItem":{"name":"Potion of climbing","weight":1,"id":"-LLf25W10AOzadTszECP"}}]}],"resultType":"sum","total":0},"signature":false,"rollid":null} I feel like from this data I should be able to understand the problem, but all my reading of the API docs and searches on the forums for similar '0' result issues hasn't given me the 'a-ha!' moment I'm looking for.  What's going on here?  Is it fixable?
1536241023
The Aaron
Pro
API Scripter
Hi Kgsodie,  Unfortunately, this is some sort of limitation on the Roll20 sendChat() side of things.  I haven't been able to track down a solution for it, so will likely have to write my own Rollable Table Expansion System and not rely on sendChat().  That's on my list of things to do, but I'm in the middle of upgrading RL jobs, so haven't had much time to work on things recently.  Hopefully I'll have more time in the fall to do this. In the mean time, the best suggestion I can make is to find a way to simplify the depth of the rolls.  Not very satisfactory, I'm afraid. =(
Been reading up this morning on the Promise object.  Is it possible that, since Promise appears to promise  to supply a value asynchronously in the future, that this is the culprit?  It's 'timing out', or something, and 0 is being supplied by default?  The res[0].content I'm logging clearly shows that a result is there, it is just 'becoming' a 0 somehow.
Also, I'm not certain what you mean by 'simplify the depth of the rolls'.  This is one table item calling a second table item, so depending on how you count that's a depth of only 2 or 3.  I'll play around some more.  Since the result of the roll is there in the log message, as I commented on earlier, it sure looks like something strange is going on. I totally get the RL thing.  I'm sure you've heard it before, but I have nothing but respect and admiration for all the work you put in on this stuff.  Your work is truly valued, I assure you!
1536242641

Edited 1536244300
Here's something interesting:  If I change the inline roll for the InjestPoison table from this: [[1t[InjestedPoison]]] to this: [[[[1d1]]t[InjestedPoison]]] It appears to fix the problem.  I ran the test 25 times and it worked every time, whereas the original version failed about 95% of the time. Again, this seems like it should be telling me something, but to me it just looks like some sort of parsing bug, either in sendChat or RecursiveTables. ***UPDATE*** I attempted this technique for other tables that had the same symptoms, but it did not fix it universally.  The investigation continues...
1536245622
The Aaron
Pro
API Scripter
Hmmm...  I was partway through writing about how the v:0  is the issue and the return from sendChat() has problems and yada, yada... However, in digging into it to give you a better answer I think my understanding of the issue has changed.  Assuming your collection of the data is correct, there is no appreciable difference between ingested poison roll that resulted in Pale Tincture, and the one that resulted in 0.  It's possible I've been conflating this behavior with a similar behavior when GroupInitiative was doing all it's rolls in a single sendChat(), and this might be a different issue.  Possibly not in Roll20's code at all.  I'll try to prioritize digging into this soon. In the meantime, if you feel adventuresome, you could try looking at what's going on in parseInlines() on line 287.  That's where all the substitutions happen, and the returned string is passed again to sendChatP() if it has more inline rolls. 
Way ahead of you, that's exactly what I'm doing.  It would help if I knew javascript better, lol.
1536257061
The Aaron
Pro
API Scripter
=D
Aaron, you right some really dense javascript.  My head hurts.  I still have no idea what is going on here, but I do believe that whatever is wrong is seen very very early. I put a log statement near the end of parseInlines: log('subs:') log(subs); if(promises.length){ Promise.all(promises) .then(()=>{ returnSubs(subs); }) .catch((e)=>{ let eRoll=HE(_.pluck(inlines,'expression').join(', ')); sendChat(`RecursiveTables`,`/w "${opts.who}" <div>An Error occurred: <code>${eRoll}</code></div><div>Error: <code>${e.message}</code></div>`); }); } else { returnSubs(subs); } And the very first thing it prints is: "subs:" {"$[[0]]":0} Everywhere else that I log, when it is processing the InjestedPoison, I can see the correct text.  I have some more log messages here in parseInlines: if(context[key].hasText && !context[key].sentinal){ log('if 3'); subs[key]=composeParts(context[key].parts); log(key); log(subs); } else { log('else 3'); subs[key]=result; log(subs[key]); } And the first time it reports the InjestedPoison, it looks like this: "if 3" "$[[2]]" {"$[[0]]":21,"$[[1]]":1,"$[[2]]":"Torpor"} My guess is that somewhere in the recursion, a returned value (i.e. Torpor) isn't replacing the original value reported at first (i.e. 0).  But I still haven't figured out exactly where or how this is happening. Lastly, I put in this logging: context[key].parts[dieIdx]={ text: text, avatar: die.tableItem.avatar || getAvatar(die.tableItem.id) }; log('key=' + key + ', dieIdx=' + dieIdx + ':' + context[key].parts[dieIdx].text); --context[key].sentinal; if(!context[key].sentinal){ subs[key]=composeParts(context[key].parts); } done(true); And here are the final 4 log messages: "subs:" {"$[[0]]":21,"$[[1]]":1,"$[[2]]":"Torpor","$[[3]]":"Potion of frost giant strength"} "key=$[[0]], dieIdx=0:A fruit tree grows. It has 21 fruit, including fruit that acts as these potions:<br/>Potion of frost giant strength<br/><br/>and one acts as an ingested poison:<br/>0<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." I'm going to keep playing with it, because as a technical problem I find it interesting and challenging, but I'm really hoping some of this sparks an idea for you...
Going back to the sendChatP logging: sendChatP = function(msg){ log(''); log('=' + msg.replace(/\[\[\s+/,'[[').replace(/\[\[\s+\[\[/,'[[[[')); return new Promise((resolve) =>{      sendChat('',msg.replace(/\[\[\s+/,'[[').replace(/\[\[\s+\[\[/,'[[[['),(res)=>{ log(res[0].type + ':' + res[0].inlinerolls.length + '==' + res[0].content); for(var ii=0; ii<res[0].inlinerolls.length;ii++) { log(res[0].inlinerolls[ii]); } resolve(res[0]); }); }); }, the first log message, where msg is being logged before sendChat is called, shows this: "=A fruit tree grows. It has [[1d10+20]] fruit, including fruit that acts as these potions:<br/>[[[[1d8]]t[Potions]]]<br/><br/>and one acts as an ingested poison:<br/>[[1t[InjestedPoison]]]<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." The second log message, where res[0].content is logged, shows this: "general:4==A fruit tree grows. It has $[[0]] fruit, including fruit that acts as these potions:<br/>$[[3]]<br/><br/>and one acts as an ingested poison:<br/>0<br/><br/>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days." You'll note that the InjestedPoison part is already 0.  The code continues to log  res[0].inlinerolls: {"expression":"1d10+20","results":{"type":"V","rolls":[{"type":"R","dice":1,"sides":10,"mods":{},"results":[{"v":3}]},{"type":"M","expr":"+20"}],"resultType":"sum","total":23},"signature":"5911f96244e35ac52fd75f26aa580757854aaaf5b8c594231c1d975f7ced3cd739b2ac8d25e3bc45de2907185f8c2fcebca97b832d66ccdf95b6b3b5cd7d7579","rollid":"-LLoDB3KozH_QrTQu7ug"} {"expression":"1d8","results":{"type":"V","rolls":[{"type":"R","dice":1,"sides":8,"mods":{},"results":[{"v":7}]}],"resultType":"sum","total":7},"signature":"3741bded3c350a60320a6d7e9800f16fe74cd2ccb43fb190b9d8ff6dd07c1b367398c3ddc25a528e8d59719fdd3a533a642f4d755909710b31f33741d9735e49","rollid":"-LLoDB3OhcyR85M3YZAI"} {"expression":"1t[InjestedPoison]","results":{"type":"V","rolls":[{"type":"R","dice":1,"table":"InjestedPoison","mods":{},"sides":5,"results":[{"v":0,"tableidx":0,"tableItem":{"name":"Assassin's Blood","weight":1,"id":"-LLjHMXgF_JhyF54Jbem"}}]}],"resultType":"sum","total":0},"signature":false,"rollid":null} {"expression":"7t[Potions]","results":{"type":"V","rolls":[{"type":"R","dice":7,"table":"Potions","mods":{},"sides":27,"results":[{"v":0,"tableidx":8,"tableItem":{"name":"Potion of water breathing","weight":1,"id":"-LLjHN6zaZJUwd2MiAvp"}},{"v":0,"tableidx":16,"tableItem":{"name":"Potion of invulnerability","weight":1,"id":"-LLjHN7Doz0HqwCbNO7p"}},{"v":0,"tableidx":4,"tableItem":{"name":"Potion of resistance","weight":1,"id":"-LLjHN6mVu-XExVSIveM"}},{"v":0,"tableidx":19,"tableItem":{"name":"Potion of healing (supreme)","weight":1,"id":"-LLjHN7Jc8o_0hTAcw1y"}},{"v":0,"tableidx":12,"tableItem":{"name":"Potion of gaseous form","weight":1,"id":"-LLjHN75tP_kF3As9pTo"}},{"v":0,"tableidx":4,"tableItem":{"name":"Potion of resistance","weight":1,"id":"-LLjHN6mVu-XExVSIveM"}},{"v":0,"tableidx":15,"tableItem":{"name":"Potion of heroism","weight":1,"id":"-LLjHN7BYriyhhapjnXy"}}]}],"resultType":"sum","total":0},"signature":false,"rollid":null} You'll see that all of these are correct.  The later logging where 1t[InjestedPoison] is resolved is also ok. To my eye, it does appear that sendChat is the culprit here.  :-(  If that is the case, I can't fix it, right?  The sad part here is that there is no 'depth'.  This is the 3rd roll on a single line.
1536352399
The Aaron
Pro
API Scripter
Quite intriguing!  Now I'm really pumped to look at this... I might have to avoid a bit of sleep tonight and dig....
For the problematic table-item: !import-table-item --BagOfBeansEffects --A fruit tree grows. It has <%%91%%><%%91%%>1d10+20<%%93%%><%%93%%> fruit, including fruit that acts as these potions:<%%60%%>br/<%%62%%><%%91%%><%%91%%><%%91%%><%%91%%>1d8<%%93%%><%%93%%>t<%%91%%>Potions<%%93%%><%%93%%><%%93%%><%%60%%>br/<%%62%%><%%60%%>br/<%%62%%>and one acts as an ingested poison:<%%60%%>br/<%%62%%><%%91%%><%%91%%>1t<%%91%%>InjestedPoison<%%93%%><%%93%%><%%93%%><%%60%%>br/<%%62%%><%%60%%>br/<%%62%%>The tree vanishes after 1 hour. Picked fruit remains, retaining any magic for 30 days. --10 -- I have discovered that if I put any straight die roll between the [[[[1d8]]t[Potions]]] and [[1t[InjestedPoison]]], this also fixes the item.  I used [[1d8]].  However, if I put this die roll anywhere else (somewhere before the first die roll, somewhere after the last die roll, or between the first die roll ([[1d10+20]]) and second die roll ([[[[1d8]]t[Potions]]]), then it still doesn't work.  Clue, or yet another red herring?
1536353140
The Aaron
Pro
API Scripter
The good thing about this is that it appears to be only the index in the source string that is ever wrong.  That would actually be great as I can detect that and account for it!  I'll see what I can't do in that space later tonight...
1536353948
The Aaron
Pro
API Scripter
So, this is probably indicative of the actual bug in the Roll20 sendChat() function.  You should see if you can get a simple reproduction of the error using a script like: on('ready',()=>{ on('chat:message', (msg)=>{ if('api' === msg.type && /^!test\b/i.test(msg.content)) { sendChat('',"A [[1d10+20]] B [[[[1d8]]t[Potions]]] C [[1t[InjestedPoison]]] D",(res)=>{ log(res[0]); }); } }); }); I'm betting the bug has to do with some internal state of processing Rollable Table results.  Something that should be getting reset and isn't.  The introduction of an interstitial dice roll might be causing that internal state to be reset for the next rollable table. 
Oooooo, 'interstitial'.  If you ever need a programming job, and would be able to move to Omaha (lol), let me know! Thanks for the help.  I do that test now.
That appears to work: { "who": "", "type": "general", "content": "A $[[0]] B $[[3]] C 0 D", "playerid": "API", "avatar": false, "inlinerolls": [ { "expression": "1d10+20", "results": { "type": "V", "rolls": [ { "type": "R", "dice": 1, "sides": 10, "mods": { }, "results": [ { "v": 7 } ] }, { "type": "M", "expr": "+20" } ], "resultType": "sum", "total": 27 }, "signature": "73e8ccd4dbb3561048abc598321d8449a241996cff2d02ac06ac6581e777f09201c753e366e7b05fff0629e6ab555210bda5c17fd20d90ac788f931c77937c38", "rollid": "-LLq8RfGF6hLIZft-vfV" }, { "expression": "1d8", "results": { "type": "V", "rolls": [ { "type": "R", "dice": 1, "sides": 8, "mods": { }, "results": [ { "v": 8 } ] } ], "resultType": "sum", "total": 8 }, "signature": "3fedf6c35d2eef1536bafee7b6bae6328035e33fe00bd49803870911faf7b8aa0abca51517339ef2fb1be4e957bf5512f6837983ad87aaf3a8573c52d3382f53", "rollid": "-LLq8RfJkTFWAub-NU0u" }, { "expression": "1t[InjestedPoison]", "results": { "type": "V", "rolls": [ { "type": "R", "dice": 1, "table": "InjestedPoison", "mods": { }, "sides": 5, "results": [ { "v": 0, "tableidx": 4, "tableItem": { "name": "Truth Serum", "weight": 1, "id": "-LLpYOK2SG8Z8_LOklf7" } } ] } ], "resultType": "sum", "total": 0 }, "signature": false, "rollid": null }, { "expression": "8t[Potions]", "results": { "type": "V", "rolls": [ { "type": "R", "dice": 8, "table": "Potions", "mods": { }, "sides": 27, "results": [ { "v": 0, "tableidx": 1, "tableItem": { "name": "Potion of climbing", "weight": 1, "id": "-LLpYOZANstzcbzx-kYi" } }, { "v": 0, "tableidx": 8, "tableItem": { "name": "Potion of water breathing", "weight": 1, "id": "-LLpYOZNQNklPsW7GCJk" } }, { "v": 0, "tableidx": 15, "tableItem": { "name": "Potion of heroism", "weight": 1, "id": "-LLpYOZaVH-B-nmQvLIz" } }, { "v": 0, "tableidx": 21, "tableItem": { "name": "Potion of speed", "weight": 1, "id": "-LLpYOZl81HeXI53pVsg" } }, { "v": 0, "tableidx": 13, "tableItem": { "name": "Potion of frost giant strength", "weight": 1, "id": "-LLpYOZWHLUuuiyfzBJw" } }, { "v": 0, "tableidx": 9, "tableItem": { "name": "Potion of healing (superior)", "weight": 1, "id": "-LLpYOZPErFkU5KW8Ep8" } }, { "v": 0, "tableidx": 2, "tableItem": { "name": "Potion of healing (greater)", "weight": 1, "id": "-LLpYOZCp3Wg3hm5Xpj5" } }, { "v": 0, "tableidx": 1, "tableItem": { "name": "Potion of climbing", "weight": 1, "id": "-LLpYOZANstzcbzx-kYi" } } ] } ], "resultType": "sum", "total": 0 }, "signature": false, "rollid": null } ] }
1536363195

Edited 1536363226
The Aaron
Pro
API Scripter
Ha! I actually just took a new job at Raytheon as a Principal Systems Engineer (with an emphasis on software engineering), so I'm good for the foreseeable future. =D  I can't really move away from Indianapolis currently anyway.  Thanks for the thought though! Incidentally, it looks like that failed (or did you mean "that seems to work at reproducing the bug" ?): "content": "A $[[0]] B $[[3]] C 0 D", That 0 should have been a $[[3]], and the $[[3]] should have been a $[[2]] !  I think we have a reproducible case!!! I'll throw this in a Game with some simple tables and see if I can get the same reproduction, then we can make a bug ticket and see about getting the devs to take a look at it.
I took the stripped down test and added it to the table with a high weight and it fails regularly, but not every time.  I ran the script a bunch more times and it seems to work every time.
1536363435
The Aaron
Pro
API Scripter
"work" === work at failing?
Duh, I was looking at the inline rolls and not the content.
I ran it 12 times and it worked 3 times and failed 9.
1536370267
The Aaron
Pro
API Scripter
So, I've built out a much more complicated reproduction script.  The key appears to be a pattern where you have: [[XdX]] [[ [[XdX]]t[table] ]] [[ Xt[table] ]] That is, some number of regular dice rolls, has to be at least one, followed by a rollable table with a nested inline roll, at least one, followed by a rollable table without a nested inline roll, at least one. The more of the first there are, the more likely it will happen.  With 9 [[XdX]] types followed by 1 each of the other 2, it has always exhibited the issue for me in test. 1 it happens ever so often, with 3-5 it happens most of the time. The interesting thing is that it seems like as long as all your rollable table rolls have a nested inline roll, it works. So writing [[ 1t[table] ]] as [[ [1d1]]t[table] ]] seems like it will always work, as long as all tables are written that way.  That's just a guess, it might just make it more subtle.  It appears that when it fails, the first table expression with the nested inline roll with get the index of the last rollable table, but that's hard to verify without writing a recursive decent parser or using dynamically generated expressions (both of which are more than I want to do!). Another interesting development is that after testing for a while, I got: There was an error communicating with the QuantumRoll server. undefined Here's my test script if you wanna try it out and tinker.  It's currently only showing failures, but you can show successes if you uncomment line 102. Script: on('ready',()=>{ assureTestTable = ()=>{ let t = findObjs({ type: 'rollabletable', name: 'test-table' })[0]; if(!t){ t = createObj('rollabletable',{ name: 'test-table' }); createObj('tableitem',{ rollabletableid: t.id, name: 'table-row' }); } }; const U = (t) => t.replace(/\[/g,`${'&'}lbrack;`).replace(/\]/g,`${'&'}rbrack;`); const S = (t) => `<div style="display:inline-block;border: 1px solid #00cc00; border-radius: 1em; background-color: #006600; padding: .25em;font-weight:bold;color:white;">${t}</div>`; const F = (t) => `<div style="display:inline-block;border: 1px solid #cc0000; border-radius: 1em; background-color: #660000; padding: .25em;font-weight:bold;color:white;">${t}</div>`; const B = (t) => `<div style="padding: .25em; border: 1px solid black; border-radius: .25em; margin: .25em;">${t}</div>`; const E = (t) => `<span style="color: #990000;">${t}</span>`; const cases = [ { msg: "[[1d6]] [[[[1d8]]t[test-table]]]", rolls: 2 }, { msg: "[[1t[test-table]]] [[[[1d8]]t[test-table]]] [[1t[test-table]]]", rolls: 3 }, { msg: "[[[[1d8]]t[test-table]]] [[1t[test-table]]]", rolls: 2 }, { msg: "[[[[[[[[1d3]]d6]]d8]]t[test-table]]]", rolls: 1 }, { msg: "[[[[[[1d6]]d8]]t[test-table]]]", rolls: 1 }, { msg: "[[[[1d8]]t[test-table]]]", rolls: 1 }, { msg: "[[1d6]] [[1t[test-table]]] [[1t[test-table]]]", rolls: 3 }, { msg: "[[1d6]] [[[[1d8]]t[test-table]]] [[1t[test-table]]]", rolls: 3 }, { msg: "[[1d8]] [[1d6]] [[[[1d2]]t[test-table]]] [[1t[test-table]]]", rolls: 4 }, { msg: "[[1d8]] [[1d6]] [[1t[test-table]]] [[[[1d2]]t[test-table]]]", rolls: 4 }, { msg: "[[1t[test-table]]] [[1d6]] [[1t[test-table]]] [[[[1d2]]t[test-table]]]", rolls: 4 }, { msg: "[[1d8]] [[1d6]] [[1t[test-table]]] [[[[1d2]]t[test-table]]] [[1t[test-table]]]", rolls: 5 }, { msg: "[[1d8]] [[1d6]] [[1d3]] [[[[1d2]]t[test-table]]] [[1t[test-table]]]", rolls: 5 }, { msg: "[[1d8]] [[1d6]] [[1d3]] [[1d8]] [[1d6]] [[1d3]] [[1d8]] [[1d6]] [[1d3]] [[[[1d2]]t[test-table]]] [[1t[test-table]]] [[1t[test-table]]] [[1t[test-table]]]", rolls:13 }, { msg: "[[1d8]] [[1d6]] [[1d3]] [[1d8]] [[1d6]] [[1d3]] [[1d8]] [[1d6]] [[1d3]] [[[[1d2]]t[test-table]]] [[[[1d1]]t[test-table]]]", rolls:11 }, { msg: "[[1d8]] [[1d6]] [[1d3]] [[[[1d2]]t[test-table]]] [[1t[test-table]]] [[1t[test-table]]]", rolls: 6 }, { msg: "[[1d8]] [[1d6]] [[1d3]] [[[[1d2]]t[test-table]]] [[[[1d3]]t[test-table]]] [[1t[test-table]]]", rolls: 6 } ]; const checkCase = (c,msg) => { let match = msg.content.match(/\$\[\[\d+\]\]/g); if(match.length !== c.rolls){ sendChat('',B(`${E(`${F('Failure')}: Expected ${c.rolls} markers, found ${match.length}`)}<br><code>${U(c.msg)}</code><br><code>${msg.content}</code>`)); } else { //sendChat('',B(`${S('Success')}: <code>${U(c.msg)}</code><br><code>${U(msg.content)}</code>`)); } }; on('chat:message', (msg)=>{ if('api' === msg.type && /^!test\b/i.test(msg.content)) { sendChat('','<div style="text-align: center;background-color: #ffcccc;font-weight:bold;font-size:1.5em;border-bottom: 3px solid red;padding: .25em;">Begin Test Battery</div>'); cases.forEach((c) => { sendChat('',c.msg,(res) => checkCase(c,res[0])); }); } }); assureTestTable(); sendChat('TestRollableTable', 'Use <code>!test</code> to test'); }); It create's its own test table and gives instructions.  Run !test to see the results. Now I'll go write a bug.
1536371045
The Aaron
Pro
API Scripter
Bug ticket:&nbsp;<a href="https://app.roll20.net/forum/post/6778746/api-sendchat-returns-bad-results-to-callback-with-rollable-tables-expressions-with-inline-rolls/#post-6778746" rel="nofollow">https://app.roll20.net/forum/post/6778746/api-sendchat-returns-bad-results-to-callback-with-rollable-tables-expressions-with-inline-rolls/#post-6778746</a>
Interesting.&nbsp; Earlier I tried changing all my tables from 1t to [[1d1]]t, but it didn't fix them all.&nbsp; It will be interesting to see what roll20 finds and how long it takes them.&nbsp; Thanks for putting this together for the ticket, I appreciate it, and thanks to you for looking into this!
1536376102
The Aaron
Pro
API Scripter
No problem! =D&nbsp; Thanks for spurring on the discussion and surfacing the details of the problem. =D