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

Adding the function of Process inLineRolls to an already built API

Some time ago, we reached out to get assistance on an API that would allow for rolling on a shifting table of difficulty.  However, the creator of the game has recently gotten someone to work on a character sheet that we can reference.  I have been working on macros and making minor tweaks to the API that was created by David M. ( Link ).  I have been hoping to eliminate the chance for error on the part of the players.  So, I made a calculation macro to determine the table that the players were to roll on, based on their stats, external modifiers, and the challenge of the action.  However, the API will not accept an inlineroll, even if it is just a simple math formula. How can I integrate the process to use an inline roll, into an already created API?  I am not very skilled at this, and have only been able to make modifications based on already present examples.  Where do I even begin?
1659016922

Edited 1659016969
timmaugh
Forum Champion
API Scripter
The simplest way to do this is to just replace the roll marker in the command line with the value of the roll. To explain... when you put an inline roll in the command line, what reaches the API isn't the value of the roll, but a marker that bears the index pointing to which roll object to reference on the message object. !somescript [[1d20]] [[1d20 + [[3d4kh1]] ]] ...becomes... !somescript $[[0]] $[[2]] And on the message object is an inlinerolls property that has 3 rolls. You just have to be able to connect those roll markers to the correct roll. Note that the markers go 0 and then 2. This is because the second roll had another nested inline roll which took the index of 1. People will use something like this to replace the markers with the values (original credit to The Aaron, I believe): // reduce all inline rolls - this rewrites the content of the msg to be the output of an inline roll rather than the $[[0]], $[[1]], etc. if (_.has(msg, 'inlinerolls')) {     msg.content = _.chain(msg.inlinerolls)         .reduce(function (m, v, k) {             m['$[[' + k + ']]'] = v.results.total || 0;             return m;         }, {})         .reduce(function (m, v, k) {             return m.replace(k, v);         }, msg.content)         .value(); } Which is a bit of an older formation (javascript can do more things natively that don't require the underscore library anymore). Written today, that would probably look like: if (msg.inlinerolls && msg.inlinerolls.length) {   msg.content = msg.inlinerolls.reduce((m, v, k) => {     m['$[[' + k + ']]'] = v.results.total || 0;     return m;   }, {}).reduce((m, v, k) => {     return m.replace(k, v);   }, msg.content); } If you want to learn a bit more about what is going on, there, I wrote this explanation . Also, if you need to do more robust things with the rolls than just extract their values, you will soon cross over into the space where using the libInline script library would make your life easier, but at this point it would probably be more than you need.
1659018678

Edited 1659018719
The Aaron
Roll20 Production Team
API Scripter
Tim's explanation is great!   Here's a more recent version of the above function I wrote that handles inline rolls (including rollable tables): const processInlinerolls = (msg) => { if(msg.hasOwnProperty('inlinerolls')){ 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(o.k,o.v), msg.content); } else { return msg.content; } }; You can add that function above handleInput in David's script, then change these lines: who = getObj('player',msg.playerid).get('_displayname'); let args = msg.content.split(/\s+/); to: who = getObj('player',msg.playerid).get('_displayname'); let args = processInlinerolls(msg) .split(/\s+/); And it should just work.
1659022055
timmaugh
Forum Champion
API Scripter
oooooohhhh... Stealing that.
1659022328
timmaugh
Forum Champion
API Scripter
LOL... I went to paste it to where I'd keep it in my "snippets"... and... ...I already had it. "You can't charge me with thieving this time, since... because... I'd already thieved it once before. Already, like."
I'll have to see if I have the necessary wisdom to work this in.  lol  Like I said, I have only been making minor changes, to this point.
1659292728
The Aaron
Roll20 Production Team
API Scripter
It will look like this when you're done: const CS = (() => { const version = '0.1.0'; //Arrays to simulate tables with success limits let CS9 = [1, 6, 31, 56, 81]; let CS8 = [1, 9, 36, 61, 86]; let CS7 = [3, 11, 41, 66, 86]; let CS6 = [3, 16, 46, 71, 89]; let CS5 = [3, 21, 51, 71, 89]; let CS4 = [3, 26, 56, 76, 92]; let CS3 = [5, 31, 61, 81, 92]; let CS2 = [5, 36, 66, 86, 94]; let CS1 = [5, 41, 71, 86, 94]; let CS0 = [5, 46, 76, 89, 96]; let CS_1 = [8, 51, 76, 89, 96]; let CS_2 = [8, 56, 81, 92, 96]; let CS_3 = [10, 61, 81, 92, 96]; let CS_4 = [10, 66, 86, 94, 98]; let CS_5 = [15, 71, 89, 94, 98]; let CS_6 = [15, 76, 92, 96, 98]; let CS_7 = [20, 81, 92, 96, 98]; let CS_8 = [20, 86, 94, 98, 100]; let CS_9 = [25, 89, 96, 98, 100]; const checkInstall = () => { log('-=> CS v'+version); }; //sendChat output formatting styles, only one style right now const _h = { inlineResult: (...o) => `<span style="font-weight: bold;padding: .2em .2em; background-color:rgba(254,246,142,1);">${o.join('')}</span>` }; const getResult = function (tableArr, roll) { if (roll>=tableArr[4]) {return "Critical Success"} if (roll>=tableArr[3] && roll<tableArr[4]) {return "Enhanced Success"} if (roll>=tableArr[2] && roll<tableArr[3]) {return "Success"} if (roll>=tableArr[1] && roll<tableArr[2]) {return "Partial Success"} if (roll>=tableArr[0] && roll<tableArr[1]) {return "Miss"} if (roll<tableArr[0]) {return "Fumble"}; } const processInlinerolls = (msg) => { if(msg.hasOwnProperty('inlinerolls')){ 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(o.k,o.v), msg.content); } else { return msg.content; } }; const handleInput = (msg) => { const scriptName = 'CS'; let roll = 0; let tableName; let tableArr; let result; if(msg.type=="api" && msg.content.indexOf("!CS") === 0 ) { try { who = getObj('player',msg.playerid).get('_displayname'); let args = processInlinerolls(msg).split(/\s+/); if (args[1]>=0) { tableName = 'CS' + parseInt(args[1]); } else { tableName = 'CS_' + -1*parseInt(args[1]); } switch(tableName){ case "CS9": tableArr = CS9; break; case "CS8": tableArr = CS8; break; case "CS7": tableArr = CS7; break; case "CS6": tableArr = CS6; break; case "CS5": tableArr = CS5; break; case "CS4": tableArr = CS4; break; case "C3": tableArr = CS3; break; case "CS2": tableArr = CS2; break; case "CS1": tableArr = CS1; break; case "CS0": tableArr = CS0; break; case "CS_1": tableArr = CS_1; break; case "CS_2": tableArr = CS_2; break; case "CS_3": tableArr = CS_3; break; case "CS_4": tableArr = CS_4; break; case "CS_5": tableArr = CS_5; break; case "CS_6": tableArr = CS_6; break; case "CS_7": tableArr = CS_7; break; case "CS_8": tableArr = CS_8; break; case "CS_9": tableArr = CS_9; break; } tableName = tableName.replace("_", "-"); roll = randomInteger(100); result = getResult(tableArr, roll); let output = '&{template:default} {{name=' + tableName + '}} {{Roll=[[' + roll + ']]}} {{Result=' + _h.inlineResult(result) + '}}'; sendChat(scriptName, output); } catch(err) { sendChat(scriptName,`/w "${who}" `+ 'Error: ' + err.message); } }; }; const registerEventHandlers = () => { on('chat:message', handleInput); }; on('ready', () => { checkInstall(); registerEventHandlers(); }); })(); =D
That is epic.  Now, I need to figure out how to get the ability ranks to translate to numbers.  The character sheet would be optimum, but we're not sure it is possible, without having player input.  If you guys can give me advice on this, it would be perfect.
1659295147
The Aaron
Roll20 Production Team
API Scripter
I don't really know the system, but if you can explain what a human would do for the conversion, I can probably translate it for the Mod Sandbox.
1659299553

Edited 1659300292
It is basically a Vs to table roll. Crimson Comet wants to try using his speed to create a ground-based sonic boom.  His speed is enough, but he needs to make an endurance roll to see if he will take damage from it.  His endurance is "IN40"(#7).  He is going to have to challenge the "AM50"(#8) damage his speed would cause.  Before we roll, we check his ability vs the opposing check to determine what table he is rolling on.  In this instance, IN40 is 1 below AM50.  So, he would be rolling on CS-1. The character sheet current has a dropdown for the rank (IN[Incredible], AM[Amazing], FE[Feeble], etc.) and a manual field for the intensity (the range of numbers that are encompassed by the rank).  I can't change "Feeble" into a 1, of "Amazing" into an 8, for the macro, and the sheet writer isn't certain on how to make it impossible for a player to fiddle with the 1 from "Feeble", or the 8 from "Amazing".  There are about 15 or 16 of these ranks, with different ranges, but the only thing that the macro needs is the rank to be a number. Basically, the macro would be something like this... !CS [[@selected|REndurance} + ?{It is modified by?|0|1|-1|2|-2|3|-3|4|-4|5|-5|6|-6|7|-7|8|-8|9|-9} - ?{The opposing rank is|Feeble,1|Poor,2|Typical,3|Good,4|Excellent,5|Remarkable,6|Incredible,7|Amazing,8|Mighty,9|Uncanny,10|Spectacular,11|Fantastic,12|Legendary,13|Wondrous,14|Epic,15|Cosmic,16}]]  ?{select Action|FightingRoll| AgilityRoll|StrengthRoll| EnduranceRoll|ReasonRoll| IntuitionRoll|PsycheRoll| DynamicRoll|ResourcesRoll| Misc} @{selected|token_name}
1659304628
The Aaron
Roll20 Production Team
API Scripter
I bet Tim can solve that with a Meta script.
Any and all help is greatly appreciated.  I m not sure whether it would be in the API or the character sheet, though.  If it is the sheet, I will need to reach out to the guy working on the sheet.
1659364005
timmaugh
Forum Champion
API Scripter
I think the metascripts can help. Just to make sure we're not leaving something behind, though, you say there is a "manual field for the intensity", but I don't see any sort of "intensity" reflected in your macro. Does that number affect the roll we are creating, here, and if so, how? Pending any sort of change to my understanding based on that answer, it sounds like you want to translate a rank designation (Feeble, Poor, Typical, etc.) into a number without having to use a query, and it looks like you need to do it inside an inline roll. That said, you are also feeding to your command line an extra argument ("select Action"), which the script is not set up to do anything with... which makes me wonder if I have the right understanding of what you're trying to do. If I don't, post back. As for that specific need (translating a rank to a number inside an inline roll)... you're going to want 2 metascripts. Muler , and ZeroFrame (both available in the 1-click). ( Quick Metascript Primer: If you don't know what metascripts are, think of them like more ways to process information in your existing command line to your CS script/mod. Roll20 lets you do something like @{selected|token_id} to get the id of a token and deliver it to your command line, but no way to get the "top" or "left" properties of a token. Roll20 lets you roll randomly against a table, but they don't let you return a specific entry from a table. These are the gaps that metascripts try to fill.) Muler will let you return a specific entry from a table (a mule), and ZeroFrame will let you delay your inline roll until all of the necessary data has been returned. Setup 0. Install the scripts. You installed the scripts, right? =D 1. Start with a macro mule character that you are comfortable with all players having access to. If you don't have such a character, create one. For this example, I'll call my character "MuleBoy". 2. On MuleBoy, grant explicit "Controlled By" rights to each player individually (there's a bug I have to chase down with having "All Players" selected, instead, so don't use that yet) 3. On MuleBoy, create a new ability. I'll name mine (for this example) "RankTable". This is our "mule". 4. Fill the mule with the table of information with the key separated from the value by an "=" sign: Feeble=1 Poor=2 Typical=3 Good=4 Excellent=5 Remarkable=6 Incredible=7 Amazing=8 Mighty=9 Uncanny=10 Spectacular=11 Fantastic=12 Legendary=13 Wondrous=14 Epic=15 Cosmic=16 Important Note: those key entries must match the value of the rank, so if you are using "FE" instead of "Feeble", make that change. If you are using "IN40" instead of "Incredible", make that change. 5. Test that setup by entering this into the chat: !The value for Feeble is get.MuleBoy.RankTable.Feeble/get.{&simple}{&mule MuleBoy.RankTable} (If you have changed the table to be "FE" instead of "Feeble", use that inside the get..../get block, instead.) You should get an output of: The value for Feeble is 1. You can try that for other entries in the table. You can also try it for a selected token, pulling the attribute that holds the rank you'd like to test. If that attribute were named "SpackleWagon", you could select that character and then do this: !The value for SpackleWagon is get.MuleBoy.RankTable.@{selected|SpackleWagon}/get.{&simple}{&mule MuleBoy.RankTable} 6. Hydrate. 7. Starting with your example macro, above... !CS [[@selected|REndurance} + ?{It is modified by... ...and assuming we want to translate the REndurance rank into a number, we would replace @{selected|REndurance} with a  get block aimed at the REndurance attribute: get.MuleBoy.RankTable.@{selected|REndurance}/get ...and we'd need to include the mule statement somewhere in the command line: {&mule MuleBoy.RankTable} Note: this won't work yet, because we have to delay the inline roll. 8. To delay the inline roll, we use a backslash character (and a closing bracket for the front set). Since we need only 1 level of delay (to let Muler process), we'll use 1 backslash. That means you'd change your brackets from this: [[ ... ]] ...to... [\][\] ... \]\] TL;DR Final Output Your macro should now look something more like: !CS [\][\]get.MuleBoy.RankTable.@{selected|REndurance}/get + ?{It is modified by?|0|1|-1|2|-2|3|-3|4|-4|5|-5|6|-6|7|-7|8|-8|9|-9} - ?{The opposing rank is|Feeble,1|Poor,2|Typical,3|Good,4|Excellent,5|Remarkable,6|Incredible,7|Amazing,8|Mighty,9|Uncanny,10|Spectacular,11|Fantastic,12|Legendary,13|Wondrous,14|Epic,15|Cosmic,16}\]\]{&mule MuleBoy.RankTable} Note that I didn't include the what-appeared-to-be-extraneous arguments you had after the initial call to CS. I just wanted to keep the example simple.
Just to make sure that I am following all of this correctly, both Muler and ZeroFrame need to be added as API's to the game.  ZeroFrame will allow for a pause between the inline roll/other processes and the API taking the information to use it.  Muler will be used to convert something (a rank) into a number to be used in the inline roll/other processes.  If this is correct, I just need to know if the "MuleBoy" ability table needs to be added to all character sheets, or just a single one for reference.  If this is not correct, I am might need it drawn in crayon.
1659386335
timmaugh
Forum Champion
API Scripter
ZeroFrame - yes (the pause). ZF will run "cycles" of the other metascripts and inline rolls. Here, we need one cycle to let the inline roll happen *after* our Muler return. MuleBoy - The ability only needs to be on one character (which I named "MuleBoy"). The ability/mule was named "RankTable". So create the RankTable ability on the MuleBoy character, and give all players who will need to run the macro explicit "Controlled By" access to the character. Give that a run and see what you get.
Do they need to be able to see it in their journal, by any chance?
1659389397

Edited 1659392424
timmaugh said: I think the metascripts can help. Just to make sure we're not leaving something behind, though, you say there is a "manual field for the intensity", but I don't see any sort of "intensity" reflected in your macro. Does that number affect the roll we are creating, here, and if so, how? Pending any sort of change to my understanding based on that answer, it sounds like you want to translate a rank designation (Feeble, Poor, Typical, etc.) into a number without having to use a query, and it looks like you need to do it inside an inline roll. That said, you are also feeding to your command line an extra argument ("select Action"), which the script is not set up to do anything with... which makes me wonder if I have the right understanding of what you're trying to do. If I don't, post back. Right, I forgot to address these two questions, on my side. First, intensity would be the range of numbers within the rank.  They are only applied to things like damage dealt or resisted.  So, easiest example to demonstrate this would be TY06.  When you start your character, you get the average of the range.  Typical ranges from 5 to 7.  So, you start at 06.  If you try to get to a Good rank, you would need to get to 8.  Even so, a GD08 and a GD10 would still be a Good rank.  So, it those little numbers only matter when you get down to details. Second, the action that I reference is part of a newer version of the API.  It gets degrees of success and bases the results on the CS and the action response.  So, the instead of seeing "Fumble" on a roll of 01 for "Stun", you would see "K.O.".  And, if I am understanding what The Aaron provided, I think that the API would look like this: const CS = (() => { const version = '0.1.0'; const successText = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; //not used let CS9 = [2, 06, 31, 56]; let CS8 = [2, 11, 36, 61]; let CS7 = [2, 16, 41, 66]; let CS6 = [2, 21, 46, 71]; let CS5 = [2, 26, 51, 76]; let CS4 = [2, 31, 56, 81]; let CS3 = [2, 36, 61, 86]; let CS2 = [2, 41, 66, 91]; let CS1 = [2, 46, 71, 96]; let CS0 = [2, 51, 76, 98]; let CS_1 = [2, 56, 81, 100]; let CS_2 = [2, 61, 86, 100]; let CS_3 = [2, 66, 91, 100]; let CS_4 = [2, 71, 96, 100]; let CS_5 = [6, 76, 98, 100]; let CS_6 = [11, 81, 98, 100]; let CS_7 = [16, 86, 98, 100]; let CS_8 = [21, 91, 98, 100]; let CS_9 = [26, 96, 98, 100]; let featRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let bluntAttack = ["Fumble", "Miss", "Hit", "Slam", "Stun"]; let edgedAttack = ["Fumble", "Miss", "Hit", "Stun", "Kill"]; let shooting = ["Fumble", "Miss", "Hit", "Bullseye", "Kill"]; let throwingEdged = ["Fumble", "Miss", "Hit", "Bullseye", "Kill"]; let throwingBlunt = ["Fumble", "Miss", "Hit", "Bullseye", "Stun"]; let energy = ["Fumble", "Miss", "Hit", "Bullseye", "Kill"]; let force = ["Fumble", "Miss", "Hit", "Bullseye", "Stun"]; let grappling = ["Fumble", "Miss", "Hit", "Partial", "Hold"]; let grabbing = ["Fumble", "Miss", "Take", "Grab", "Break"]; let escaping = ["Fumble", "Miss", "Miss", "Escape", "Reverse"]; let charging = ["Fumble", "Miss", "Hit", "Slam", "Stun"]; let dodging = ["Autohit", "None", "-1CS", "-2CS", "-4CS"]; let evading = ["+Stun", "Autohit", "Evasion", "+1CS", "+2CS"]; let blocking = ["+Stun", "-6CS", "-4CS", "-2CS", "-1CS"]; let catching = ["+Stun", "Autohit", "Miss", "Damage", "Catch"]; let stun = ["+K.O.", "1-10", "1", "No", "No"]; let slam = ["+Stun", "Grand Slam", "1 Area", "Prone", "No"]; let ko = ["+Kill", "Knockout", "+Stun", "No", "No"]; let bleed = ["+Kill", "Bleed", "-1", "No", "No"]; let kill = ["KIA", "Endurance Loss", "E/S", "No", "No"]; let rollWithTheBlow = ["+1CS", "No", "-1CS", "-2CS", "-3CS"]; let bounceBack = ["+Stun", "No", "-1CS", "End", "+1CS"]; let fightingRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let agilityRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let strengthRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let enduranceRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let reasonRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let intuitionRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let psycheRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let dynamicRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let resourcesRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let miscRoll = ["Fumble", "Miss", "Partial Success", "Success", "Critical Success"]; let oneextraAction = ["Fumble", "Miss", "Success", "Success", "Success"]; let twoextraActions = ["Fumble", "Miss", "Success", "Success", "Success"]; let threeextraActions = ["Fumble", "Miss", "Success", "Success", "Success"]; let firstTry = ["Failure", "Failure", "Failure", "Failure", " First Success"]; let oneSuccess = ["Failure", "Failure", "Failure", "Second Success", "Second Success"]; let twoSuccesses = ["Failure", "Failure", "Third Success", "Third Success", "Third Success"]; let threeSuccesses = ["Failure", "Failure", "Fourth Success", "Fourth Success", "Fourth Success"]; let fourSuccesses = ["Failure", "Failure", "Fifth Success", "Fifth Success", "Fifth Success"]; let fiveSuccesses = ["Failure", "Failure", "Sixth Success", "Sixth Success", "Sixth Success"]; let sixSuccesses = ["Failure", "Failure", "Seventh Success", "Seventh Success", "Seventh Success"]; let sevenSuccesses = ["Failure", "Failure", "Eighth Success", "Eighth Success", "Eighth Success"]; let eightSuccesses = ["Failure", "Failure", "Ninth Success", "Ninth Success", "Ninth Success"]; let nineSuccesses = ["Failure", "Failure", "Final Success", "Final Success", "Final Success"]; const checkInstall = () => { log('-=> CS v'+version); }; //sendChat output formatting styles const _h = { fumble: (...o) => `<span style="font-weight: bold;padding: .2em .2em; background-color:rgba(1,178,240,1);">${o.join('')}</span>`, miss: (...o) => `<span style="font-weight: bold;padding: .2em .2em; background-color:rgba(255,255,255,1);">${o.join('')}</span>`, partial: (...o) => `<span style="font-weight: bold;padding: .2em .2em; background-color:rgba(1,192,1,1);">${o.join('')}</span>`, success: (...o) => `<span style="font-weight: bold;padding: .2em .2em; background-color:rgba(255,254,1,1);">${o.join('')}</span>`, crit: (...o) => `<span style="font-weight: bold;padding: .2em .2em; background-color:rgba(255,62,62,1);">${o.join('')}</span>` }; const formattedResult = function (successLevel, text) { switch(successLevel){ case 0: return _h.fumble(text); break; case 1: return _h.miss(text); break; case 2: return _h.partial(text); break; case 3: return _h.success(text); break; case 4: return _h.crit(text); break; } } const getSuccessLevel = function (tableArr, roll) { if (roll>=tableArr[3]) {return 4} //"Critical Success" if (roll>=tableArr[2] && roll<tableArr[3]) {return 3} //"Success" if (roll>=tableArr[1] && roll<tableArr[2]) {return 2} //"Partial Success" if (roll>=tableArr[0] && roll<tableArr[1]) {return 1} //"Miss" if (roll<tableArr[0]) {return 0} //"Fumble" }; const processInlinerolls = (msg) => { if(msg.hasOwnProperty('inlinerolls')){ 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(o.k,o.v), msg.content); } else { return msg.content; } }; const handleInput = (msg) => { const scriptName = 'CS'; let roll = 0; let tableName; let tableArr; let actionArr; let action = ""; let successLevel; let result; if(msg.type=="api" && msg.content.indexOf("!CS") === 0 ) { try { who = getObj('player',msg.playerid).get('_displayname'); let args = msg.content.split(/\s+/); if (args[1]>=0) { tableName = 'CS' + parseInt(args[1]); } else { tableName = 'CS_' + -1*parseInt(args[1]); } //Get Success Level Array switch(tableName){ case "CS9": tableArr = CS9; break; case "CS8": tableArr = CS8; break; case "CS7": tableArr = CS7; break; case "CS6": tableArr = CS6; break; case "CS5": tableArr = CS5; break; case "CS4": tableArr = CS4; break; case "CS3": tableArr = CS3; break; case "CS2": tableArr = CS2; break; case "CS1": tableArr = CS1; break; case "CS0": tableArr = CS0; break; case "CS_1": tableArr = CS_1; break; case "CS_2": tableArr = CS_2; break; case "CS_3": tableArr = CS_3; break; case "CS_4": tableArr = CS_4; break; case "CS_5": tableArr = CS_5; break; case "CS_6": tableArr = CS_6; break; case "CS_7": tableArr = CS_7; break; case "CS_8": tableArr = CS_8; break; case "CS_9": tableArr = CS_9; break; } //Get Action Array action = args[2]; switch(args[2]){ case "FeatRoll": actionArr = featRoll; break; case "BluntAttack": actionArr = bluntAttack; break; case "EdgedAttack": actionArr = edgedAttack; break; case "Shooting": actionArr = shooting; break; case "ThrowingEdged": actionArr = throwingEdged; break; case "ThrowingBlunt": actionArr = throwingBlunt; break; case "Energy": actionArr = energy; break; case "Force": actionArr = force; break; case "Grappling": actionArr = grappling; break; case "Grabbing": actionArr = grabbing; break; case "Escaping": actionArr = escaping; break; case "Charging": actionArr = charging; break; case "Dodging": actionArr = dodging; break; case "Evading": actionArr = evading; break; case "Blocking": actionArr = blocking; break; case "Catching": actionArr = catching; break; case "Stun": actionArr = stun; break; case "Slam": actionArr = slam; break; case "KO": actionArr = ko; break; case "Bleed": actionArr = bleed; break; case "Kill": actionArr = kill; break; case "RollWithTheBlow": actionArr = rollWithTheBlow; break; case "BounceBack": actionArr = bounceBack; break; case "FightingRoll": actionArr = fightingRoll; break; case "AgilityRoll": actionArr = agilityRoll; break; case "StrengthRoll": actionArr = strengthRoll; break; case "EnduranceRoll": actionArr = enduranceRoll; break; case "ReasonRoll": actionArr = reasonRoll; break; case "IntuitionRoll": actionArr = intuitionRoll; break; case "PsycheRoll": actionArr = psycheRoll; break; case "DynamicRoll": actionArr = dynamicRoll; break; case "ResourcesRoll": actionArr = resourcesRoll; break; case "Misc": actionArr = miscRoll; break; case "OneExtraAction": actionArr = oneextraAction; break; case "TwoExtraActions": actionArr = twoextraActions; break; case "ThreeExtraActions": actionArr = threeextraActions; break; case "FirstTry": actionArr = firstTry; break; case "1Success": actionArr = oneSuccess; break; case "2Successes": actionArr = twoSuccesses; break; case "3Successes": actionArr = threeSuccesses; break case "4Successes": actionArr = fourSuccesses; break case "5Successes": actionArr = fiveSuccesses; break case "6Successes": actionArr = sixSuccesses; break case "7Successes": actionArr = sevenSuccesses; break case "8Successes": actionArr = eightSuccesses; break case "9Successes": actionArr = nineSuccesses; break default: sendChat(scriptName,`/w "${who}" `+ 'Error: Action \"' + action + '\" not found.'); break } tableName = tableName.replace("_", "-"); roll = randomInteger(100); successLevel = getSuccessLevel(tableArr, roll); result = actionArr[successLevel]; //let output = '&{template:default} {{name=' + tableName + '}} {{Roll=[[' + roll + ']]}} {{SuccessLevel=' + _h.inlineResult(result) + '}}'; let output = '&{template:default} {{name=' + action + ' ' + tableName + '}} {{Roll=[[' + roll + ']]}} {{Result=' + formattedResult(successLevel, result) + '}}'; sendChat(scriptName, output); } catch(err) { sendChat(scriptName,`/w "${who}" `+ 'Error: ' + err.message); } }; }; const registerEventHandlers = () => { on('chat:message', handleInput); }; on('ready', () => { checkInstall(); registerEventHandlers(); }); })(); At least, that is what I am hoping. ETA: Macros have failed and I get this message:
1659400819
timmaugh
Forum Champion
API Scripter
Damn Eggo II said: Do they need to be able to see it in their journal, by any chance? I don't think so, if I remember correctly. I can test later, though.
1659401191
timmaugh
Forum Champion
API Scripter
Damn Eggo II said: Right, I forgot to address these two questions, on my side. First, intensity would be the range of numbers within the rank.  They are only applied to things like damage dealt or resisted.  So, easiest example to demonstrate this would be TY06.  When you start your character, you get the average of the range.  Typical ranges from 5 to 7.  So, you start at 06.  If you try to get to a Good rank, you would need to get to 8.  Even so, a GD08 and a GD10 would still be a Good rank.  So, it those little numbers only matter when you get down to details. Second, the action that I reference is part of a newer version of the API.  It gets degrees of success and bases the results on the CS and the action response.  So, the instead of seeing "Fumble" on a roll of 01 for "Stun", you would see "K.O.".  And, if I am understanding what The Aaron provided, I think that the API would look like this: <snip> ETA: Macros have failed and I get this message: I had not seen that much of the script before... So I'll read through it and see how/where it is expecting the other components. As for the error, ZeroFrame requires the libInline library. You should have gotten it automatically if you used the 1-click installer. If that didn't work, I have to submit a fix! Can you tell me how you got the ZeroFrame script? Either way, you'll need libInline. It's also available from the 1-click.
1659402781

Edited 1659404947
I made the mistake of manually adding the API.  I tend to do that, in the event that some tweaking is needed for the specific game setting.  After adding it from the library, the issue has changed from an API error in the sandbox to an error in the chat.  New issue is that it is not accepting the inline rolls, I think.  I am not 100%, but I have seen this error before. This is the macro that I am currently using: !CS [\][\]get.MacroHero.RankMacro.@{selected|VAgility}/get + ?{It is modified by?|0|1|-1|2|-2|3|-3|4|-4|5|-5|6|-6|7|-7|8|-8|9|-9} - ?{The opposing rank is|Feeble,1|Poor,2|Typical,3|Good,4|Excellent,5|Remarkable,6|Incredible,7|Amazing,8|Mighty,9|Uncanny,10|Spectacular,11|Fantastic,12|Legendary,13|Wondrous,14|Epic,15|Cosmic,16}\]\]{&mule MacroHero.RankMacro} ?{select Action|Shooting|ThrowingEdged|ThrowingBlunt|Energy|Force|Dodging|Evading|Blocking|Catching} @{selected|character_name} When I run it without the API call for CS, this is the result: [\][\]get.MacroHero.RankMacro.Good/get + 0 - 5\]\]{&mule MacroHero.RankMacro} Energy
1659413515
timmaugh
Forum Champion
API Scripter
That error is coming from CS, which means by then the metascripts are done with what they are doing. So. There are a couple of ways to test the output from the metascript work to make sure you're getting what you want. First, the metascripts are only triggered if the command line begins with an exclamation point, so if you remove that whatever you type will just hit the chat. So make sure you include it. Then, the first test would be to add {&simple} somewhere in the command line (just at the end will be fine). That allows the metascripts to process, but stops API processing before normal scripts get the message. Instead, it will output the state of the command line to a chat message... showing you what CS would have received. The {&simple} syntax is a part of ZF, not standard roll20 functionality. If the command line is what you want to see -- it's structured the way you expect -- then the problem is in CS and how it's parsing those args. However, if the line is NOT what you want to see, you can continue to the second test. For the second test, add {&log} to the command line (again, at the end). You can remove the {&simple} syntax if you want, but that doesn't matter (it will just stop CS from processing). Send the macro through, and you should get a log panel in the chat. Each entry is how that metascript left the command when it was done, so you can see where it might be going wrong. Remove the log syntax when you're done troubleshooting. Share a screenshot of the log panel, if you can't understand what you're seeing. Or, if you learn that it's the CS script throwing the error, let us know and we can get to the bottom of it.
This is the results of tests 1 and 2.  So, yeah, issue in the CS API.  Probably has to do with the second argument that wasn't taken into account when The Aaron handled the original, which only had the one argument, I think.
I think I found where I went wrong.  I went back over everything that you two have been giving me and made sure I was doing it right.  I skipped a step when I was working on the API from Aaron's post.  I missed the replacement section and didn't add it in.  After adding it in, it seems to work fine.  I think you two managed to solve a problem I have been working on for weeks. And you did it in just a couple of days.  Thank you most kindly for drawing it in crayon for me!
Know of a way to limit it to a max of 9 or a min of -9?  I forgot that some people might actually have that large of a gap.
1659453503

Edited 1659453564
timmaugh
Forum Champion
API Scripter
Yep. You can do this with a normal roll using the keep high/low mechanic, provided you turn your bounding values (-9, 9) into rolls, themselves. As a test, you can run this: &{template:default}{{name=Test}}{{Final Output=[[{[[{[[1d20-?{How much?|10}]],[[9]]}kl1]],[[-9]]}kh1]]}}{{Roll0=$[[0]]}}{{Roll1=$[[1]]}}{{Roll2=$[[2]]}}{{Roll3=$[[3]]}}{{Roll4=$[[4]]}} In your output, your "Final Roll" should always be 9 if "Roll 0" is over 9, and it should always be -9 if "Roll 0" is below -9. Otherwise, for any value where "Roll 0" is between those two boundaries, "Final Roll" should match it. I put the query in there so you can really push the numbers one way or the other. Point is, you can use this formula and sub-in your part: [[{[[{ ...your roll here... ,[[9]]}kl1]],[[-9]]}kh1]] Now, because that is an inline roll that will be containing your existing inline roll, and because your existing roll is delayed/deferred one time, we have to delay this one. Basically: anything that relies on your delayed processing will need to be delayed, too. So let's defer all of that... [\][\]{[\][\]{ ...your roll here... ,[\][\]9\]\]}kl1\]\],[\][\]-9\]\]}kh1\]\] Which means, if I have it right (and using your last example, above), the final output should be: [\][\]{[\][\]{[\][\]get.MacroHero.RankMacro.@{selected|VAgility}/get + ?{It is modified by?|0|1|-1|2|-2|3|-3|4|-4|5|-5|6|-6|7|-7|8|-8|9|-9} - ?{The opposing rank is|Feeble,1|Poor,2|Typical,3|Good,4|Excellent,5|Remarkable,6|Incredible,7|Amazing,8|Mighty,9|Uncanny,10|Spectacular,11|Fantastic,12|Legendary,13|Wondrous,14|Epic,15|Cosmic,16}\]\],[\][\]9\]\]}kl1\]\],[\][\]-9\]\]}kh1\]\]
And, that did it.  Short of begging for help if/when the game gets its own templates, I think you guys have managed to solve every problem I could anticipate, with the macros and APIs.  Like miracle works, with keyboards and skills!  Thank you, again!
1659454867
timmaugh
Forum Champion
API Scripter
Excellent! Glad you got it working!
Yep.  Now, I am translating all of this into sets of macros.  I think the only one that is giving me trouble is a generic one that would need to reference an ability based on a drop down.  I'm not gonna make it overly complicated.  So, I'll make that one something that the player has some control over.  No referencing power.  Just a manual entry of the info by the player.  Everything else is a dream.