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 .
×

Processing Die Rolls in A Mod - I May Have Written Myself into a Corner

1779930421

Edited 1779930466
This is for AD&D 1e, but the buttons I want to click on are for processing, hopefully, a die roll. If successful, I'd like to roll other dice. I'm not sure the lengthy script I've written needs to be listed here, but it does - so far - produce the black background Turn Undead chat shown here after clicking the Undead Button in the previous chat result.  I'm not sure I can do what I described above. Derp on me. A little more detail follows after the screenshot. Yes, this is for turning undead. Yes, the character sheet for AD&D 1e can handle this, but I'm not comfortable with how it works (it rolls the turning dice and the effects if successful all at once).  Most importantly, some of my players are dumb as ditch dirt (dear old friends from as far back as the 60s and 70s). :-) What I'm trying to do with the buttons that have a number instead of T or D, is to have the Mod I am working on roll a d20 if clicked. If the result is equal to or greater than the number showing on the button, I'd like to indicate success on a further chat that then rolls both a d12 (for number of the undead type affected) and 3d4 (for the number of minutes or rounds the the turning lasts). If the roll is less than the number indicated on the button, I want to indicate failure to turn the indicated undead. I'm not sure I can do this in a Mod. Oh dear. Any hints? Thanks for any help or a straight out blowing away any expectation of doing this. I'd like to stay away from ScriptCards (which I have as one of the scripts in this game), if possible,  because I don't know how to make a scriptCard in anything but a macro.
1779936137

Edited 1780015455
timmaugh
Pro
API Scripter
It's all good, Tim. You've got plenty of room to do what you want to do. To be clear, though, when you speak of "script" I am understanding you to have written some amount of javascript code that you are invoking by some text handle through chat. For instance: !thereAreSomeWhoCallMe_Mod If that's the case, then your mod script has already built the above panel... which means that it knows the value of each of the undead types (like that "Vampire" is a 4). Now, while you can do this with ScriptCards or metascripts through chat, you indicated that you wanted to do this in your script, so let's stay there. Also, a script cannot generate the 3D dice on the game board, so you don't *have* to do it as a die roll (you can call the randomInteger() function with the size of die you want to roll: randomInteger(20) ...and assign it to a variable you use for your calculations. But you said "script" and you said "roll", so let's do it. There are two things you have to do to handle the dice you roll: locate the roll in your command line extract the value from the inline roll If you already know how to do the former, skip to the section on the latter. Locating the Roll What you're going to want is a way to break your command line down so you can read the various data components. Once you have that, you can construct the command line of the buttons so that they will send an appropriately formatted message. While you could format your command line however makes sense to you (you just have to be able to "encode" AND "decode" it, or "build" and "read" it), it really comes down to a handful of levers you can pull, in this case... something like: !undeadify --Vampire --[[1d20]] !undeadify --type=Vampire --roll=[[1d20]] !undeadify --type=Vampire --target=4 --roll=[[1d20]] The data is divided to segments separated by "white space plus a double hyphen". That let's us split the command line to read the data. After that, it's a matter of answering questions: Is the line going to ALWAYS be built the same (that is, will it only ever be run from your menu, built by your script, so you know the order of arguments will never change)? Is the target number for the undead type ALWAYS going to be the same? Or is it generated by some in-game effect (ie, this time the Vampire target might be 4, but next time it might be 5, or 8)? What other command pathways are you going to have to handle beyond this success check? (For instance, would there be a follow up menu with more buttons to click depending on situation?) Just for demonstration, let's say that your answer to #1 is YES (your command line will always be constructed the same way), and the answer to #2 is also YES/FIXED (a fixed value that is the same every time you roll against a Vampire type). Since you mentioned that the further roll of the 1d12 and the 3d4 should be automatic based on success of the first 1d20, I'm not seeing that as another button that the user has to click, so for now the answer to #3 is NA (there are no other commands you have to handle). That means that your command line can be the simplest (the one at the top): !undeadify --Vampire --[[1d20]] Importantly, your script is going to see that as referring to data: !handle --undeadType --checkRoll So now you can split the line on the white-space-double-hyphen, and reference the parts as necessary: let args = msg.content.split(/\s*--/); // args[0] is the "handle": !undeadify // args[1] is the "undeadType": Vampire // args[2] is the "checkRoll": $[[0]] Alternatively, this is functionally the same, but gives you named variables for the parts: let [handle, undeadType, checkRoll] = msg.content.split(/\s*--/); // handle: !undeadify // undeadType: Vampire // checkRoll: $[[0]] Extract the Value from the Roll If you can locate the roll in your command line, you're halfway there. Now you have to extract the value. There are a couple of ways to do that. First, some verbiage. The roll is implemented above (ie, [[1d20]] ) is an inline roll . When it goes through the Roll20 parsers, you're left with a roll marker , which is a formation like: $[[0]] ...where the number between the double brackets is the roll index . All of the data for an inline roll (as long as you're not using a beacon sheet like the 2024 sheet) is on the message object in the inlinerolls property. The property contains an array where each roll is ordered according to its roll index (so $[[0]] refers to the first roll in the array, $[[1]] refers to the second, etc.). All of the roll data is there, but it's a bit... "WHERE'S THE SOIL?!" It's a bit everywhere. So your first option is to process the rolls in the command line down to their values right in the command line... If the d20 rolled a 14 and was assigned the roll index of 0 (the first roll), then processing the rolls in the command line would replace every instance of $[[0]] with 14. Then, when you went to parse the line and retrieve the "checkRoll" value (the result of the d20 roll), the text would already have been changed to be 14 rather than $[[0]]. The Aaron shared a code snippet that does that. He might have updated it from this version (to handle extended edge cases), but for what you're doing, this will work: const escapeRegExp = (string) => { return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); }; const processInlinerolls = (msg) => {   if (msg.inlinerolls && msg.inlinerolls.length) {     return msg.inlinerolls       .reduce((m, v, k) => {         let ti = v.results.rolls.reduce((m2, v2) => {           if (v2.hasOwnProperty('table')) {             m2.push(v2.results.reduce((m3, v3) => [...m3, (v3.tableItem || {}).name], []).join(", "));           }           return m2;         }, []).join(', ');         return [...m, { k: `$[[${k}]]`, v: (ti.length && ti) || v.results.total || 0 }];       }, [])         .reduce((m, o) => m.replace(new RegExp(escapeRegExp(o.k), 'g'), o.v), msg.content);   } else {     return msg.content;   } }; The processInlineRolls function takes the message object, and returns the modified command line string with the values from the dice swapped into their places. That means you can swap it into your line parsing: let args = processInlineRolls(msg).split(/\s*--/); ...OR... let [handle, undeadType, checkRoll] = processInlineRolls(msg).split(/\s*--/); ...and now wherever you would refer to the roll, you are no longer reading the roll marker ($[[0]]), you are reading the value of the roll. Your other option for getting the data from the roll would be to use something like libInline (a library script you can install and reference as a dependency). That option does make for simpler code, but it's a bit like bringing a nuke to a knife fight, for this application, so I'll just include the link to the original write up of its usage and let you decide what you want to do. Post back if any of this isn't clear or you have more questions.
1779940921

Edited 1779941022
Gauss
Forum Champion
I am a little unclear as to why this needs a mod. Couldn't this be done with templated rolls? A success/failure check can be used to zero out the effects if successful after the turn check.  Example (not accurate, just an example):  &{template:default} {{name=Test}} [[[[[[1d20]]>11]]*2d6]] {{Check=$[[0]]}} {{Effect=$[[2]]}} It will display the d20 roll, then it will display either a 0 (failure) or the result of 2d6 (success).  Could go a step further and have a success/failure statement by using a Rollable Table. Again, this is an example, I don't fully remember the AD&D1e system for turning undead. 
Gauss said: I am a little unclear as to why this needs a mod. Couldn't this be done with templated rolls? A success/failure check can be used to zero out the effects if successful after the turn check.  Example (not accurate, just an example):  &{template:default} {{name=Test}} [[[[[[1d20]]>11]]*2d6]] {{Check=$[[0]]}} {{Effect=$[[2]]}} It will display the d20 roll, then it will display either a 0 (failure) or the result of 2d6 (success).  Could go a step further and have a success/failure statement by using a Rollable Table. Again, this is an example, I don't fully remember the AD&D1e system for turning undead.  Thanks Gauss, I do use template rolls from the AD&D sheet in a number of macros and API/Mod scripts. But I do not like the undead one in this sheet because it has all dice rolling at the same time, regardless of the success of the return roll. Plus, I'm a masochist.... Besides, I wanted a nice scary black background - I could use @{selected|turn_undead_roll} if I get desperate, but I'll see what Timmaugh has for me. :-)
1780080549

Edited 1780080882
timmaugh said: Now, while you can do this with ScriptCards or metascripts through chat, you indicated that you wanted to do this in your script, so let's stay there. Also, a script cannot generate the 3D dice on the game board, so you don't *have* to do it as a die roll (you can call the randomInteger() function with the size of die you want to roll: randomInteger(20) ...and assign it to a variable you use for your calculations. Hi Timmaugh, I hope you can follow my babble here. First off, thank you for taking the time here. I continued on for a bit and managed to work something limited out. Any specification of a roll of a d20 comes last after the chat is produced. Also, as you might notice, the darn chat that shows the undead monsters that can be turned repeats itself when I press one of its buttons for some stupid reason, but I'll try to deal with that in another, separate question: So that works (apart from the double chat), but I'm intrigued by some of your responses. I was unable to get the The randomInteger() function to work from a chat button. I tried it with and without the backtick, with double and single brackets and got either an error (with the square brackets) or, without any square brackets, the image following the sendChat, below instead of the above screenshot: sendChat(udPlural, "`randomInteger(20)"); FWIW, here is the code for the Mod involved. First, there is another Mod that creates the chat with the buttons with this sort of code: function undeadTable (char_level, char_Colour) { //charlevel is effLevel fed from main script and is a number check that //sendChat("System undeadTable", char_Colour) let undeadbuttons = ""; if (char_level === 1) { undeadbuttons = "<div style='text-align:center;'>" + "<p>[Skeleton (10)](!turnundeadroll --Skeleton --10 --" + char_Colour + ")  " + "[Zombie (13)](!turnundeadroll --Zombie --13 --" + char_Colour + ")</p>" + "<p>[Ghoul (16)](!turnundeadroll --Ghoul --16 --" + char_Colour + ")  " + "[Shadow (19)](!turnundeadroll --Shadow --19 --" + char_Colour + ")</p>" + "[Wight (20)](!turnundeadroll --Wight --20 --" + char_Colour + ")" + "</div>"; } else if (char_level === 2) { etc, etc for various levels of the cleric or paladin who tries to trun undead You can see the above, which is from TurnUndead.js, calls !turnundeadroll with 3 arguments for undead, target number (or letter) and the colour the character the user has chosen for their roll template (a feature in the AD&D 1e sheet, not sure if it's common with other sheets). That is a different script, i.e.  !turnundeadroll is as follows. You can see I've commented out sendChat(udPlural, "`randomInteger(20)");. const Panel is nice formatting thing I got from user ROTTO. //Called from !turnundead in TurnUndead.js //The nature of calling here means that verification of the character is // NOT necessary. All this does is evaluate the 2 arguments passed. on('ready', () => { //Version information const ver = "Version 1" const verdate = "27 May 2026" //The following will be outputted after a restart sandbox or save: log("═╣ Turn Undead Roll by Tim, " + ver + ", " + verdate + " ╠═"); on("chat:message", function(msg) { if (msg.type !== "api") return; let args = msg.content.trim().split(/\s*--/); if (args[0].toLowerCase() !== "!turnundeadroll") return; let udTarget = args[1]; let udRoll = args[2]; let udColour = args[3]; //sendChat("System", udTarget + " " + udRoll + " " + udColour); let udTitle = ""; let udBody =""; const panel = (usercolour, title, body) => '<div style="background:' + udColour + ';border:1px solid #5b4632;border-radius:10px;padding:2px;max-width:760px;color:#f7f1e6;box---Ghoul:0 2px 6px rgba(0,0,0,0.35);">' + '<div style="font-size:22px;font-weight:bold;text-align:center;margin:0 0 10px 0;font-family:Georgia,serif;color:#fff8ec;line-height:1.15;word-break:break-word;">' + title + '</div>' + '<div style="background:#000000;color:#FFFFFF;padding:12px;border-radius:6px;">' + body + '</div>' + '</div>'; //Get plural of udTarget let udPlural = udTarget; if (udTarget ==="Mummy") { udPlural = "Mummies"; } else if (udTarget === "Lich") { udPlural = "Liches"; } else if (udTarget === "Special") { udPlural = "Special"; } else { udPlural = udPlural +"s"; } if (!isNaN(Number(udRoll))) { // The string is numeric so a roll to turn is required sendChat(udPlural, "/Roll d20>" + udRoll); //sendChat(udPlural, "`randomInteger(20)"); udTitle = "<p>If d20 Roll is " + udRoll if (udRoll < 19) {udTitle += " or greater";} udTitle += ",<br><i>Click the button below<i></p>"; udBody = "<div style='text-align:center;'>[" + udPlural + " Affected, Duration](!
#UndeadE)</div>"; sendChat(udPlural, panel(udColour, udTitle, udBody)); } else if (udRoll === "T") { //Automatically turns, no roll to turn required udTitle = "<p>" + udPlural + " Automatically Turned<br><i>Click the button below</i></p>" udBody = "<div style='text-align:center;'>[" + udPlural + " Affected, Duration](!
#UndeadE)</div>"; sendChat(udPlural, panel(udColour, udTitle, udBody)); } else if (udRoll === "D") { //Automatically destroys/disrupts, no roll to turn required udTitle = "<p>" + udPlural + " Automatically Destroyed<br><i>Click the button below</i></p>" udBody = "<div style='text-align:center;'>[" + udPlural + " Destroyed](!
#UndeadD)</div>"; sendChat(udPlural, panel(udColour, udTitle, udBody)); } else { //Automatically destroys/disrupts, 7-12 is number instead of 1-12 udTitle = "<p>" + udPlural + " Automatically Destroyed<br><i>Click the button below</i></p>" udBody = "<div style='text-align:center;'>[" + udPlural + " Destroyed](!
#UndeadD7)</div>"; sendChat(udPlural, panel(udColour, udTitle, udBody)); } return; }); }); Anyway, I'm clearly using randomInteger() incorrectly. I'll get to the other points in your posts after I figure out randomInteger(). I hope this is not overwhelming. Thanks so far.
1780111197
timmaugh
Pro
API Scripter
Thanks for providing your code; it makes it much clearer to refer to actual/specific things rather than talk in abstract possibilities. =D So, randomInteger() is only going to work from within javascript; you can't send it through chat and expect it to resolve. It's a javascript function provided by the Roll20 sandbox. By way of example of how to use it, let's rejigger your workflow just slightly. I propose: You run (by some command) TurnUndead, which produces a panel. That panel has buttons for the various undead types and their target number, including "Ghoul (16)" You click the Ghoul button, which sends a message through chat with the handle, the type, and the target TurnUndeadRoll catches the message and starts to work It generates a number via randomInteger(), and compares it against the target number Condition 1: if that number is less-than-or-equal to your target, report the result; provide no button Condition 2: if that number is greather-than-or-equal to your target, report the result AND provide a button to run the #UndeadE macro Alternatively, you could also generate your d12 and 3d4 in code and include them in the same report, if you wanted to save having to click another button To do that, we're going to change your IF block that starts around line 49. Currently, it looks like (contents snipped out): if (!isNaN(Number(udRoll))) { // The string is numeric so a roll to turn is required // ... } else if ( /* ... */ ) { // Automatically turns, no roll to turn required Change that to something more like: let udRandom = randomInteger(20); if (!isNaN(Number(udRoll))) { // The string is numeric so a roll to turn is required   if (udRandom >= Number(udRoll)) { // beat the target     udTitle = `<p>Success!</p>`;     udBody = `<p>${udRandom} beats ${udRoll}. Click the button, below.</p><div style='text-align:center;'>[${udPlural} Affected, Duration](!
#UndeadE)</div>`;   } else { // did not beat the target     udTitle = `<p>Failure!</p>`;     udBody = `<p>You fail to turn any undead.</p>`;   }   sendChat(udPlural, panel(udColour, udTitle, udBody)); } else if (udRoll === "T") { //Automatically turns, no roll to turn required   // ... That will generate a value between 1 and 20 for the "roll" and compare it against the target. Only if the roll succeeds do yo use the button. Again, if you wanted to save having to click on the button, you could also generate the d12 and 3d4, then report them: let udCount = randomInteger(12); let udDuration = randomInteger(4) + randomInteger(4) + randomInteger(4); Hopefully that makes sense. If you get your arms around that and want to see how handling inline rolls would work differently, post back.