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

FASERIP API - Anyone wanna help me understand and expand this script?

I spoke with the author Sarah A. and she is not continuing the script.  My friends and I would like to use the script and expand it for further automation (like auto rolling stun when the results is stun).  However, I am very new to javascript.  Anyone willing to answer questions specific to the script and help me understand how to add new features?
before i post this code.  are there code tags here?
// UNIVERAL TABLE DICE ROLLER FOR MARVEL SUPER HEROES RPG // Last Update: May, 2015 // Version: 1.0 // Rolls should be structured as "!ut <rank name> <column shift> <attack type> --roll:<roll name> --id:<character id>" // <rank name> must come first after the "!ut" API declaration // <column shift> and <attack type> can come in any order, but must be after the <rank name> and before the options for <roll name> and <character id> // Make sure there are no spaces in the <rank name>. Ex: Shift 0 becomes Shift0 or Shift-0 // --roll:<roll name> can be used to add a title to your roll when using Roll Templates; optional but must come after <rank name>, <column shift>, and <attack type> // --id:<character id> can be used to put the character's name into the Roll Template; optional but must come after <rank name>, <column shift>, and <attack type> //Use !help for reminders on how to use the script //Use !example for examples of how to use the script // Use !attack to print out the list of abbreviations for attack types // TO CHANGE ROLL TEMPLATE: // Scroll down to the bottom of the script // Find the section that says CHOOSE YOUR TEMPLATE HERE! // There are three options each with a header that states what that option is // Each option starts with 'template =' // Remove the // in front of the option you wish to use, the text should turn white // MAKE SURE TO PUT // in front of any options you DO NOT want to use, the text should turn gray // Hit the 'Save Script' button "use strict"; on("chat:message", function(msg) { /* HELPER MESSAGES */ if(msg.type == "api" && (msg.content.indexOf("!attack") !== -1 || msg.content.indexOf("!help") !== -1 || msg.content.indexOf("!example") !== -1)) { var lines = []; var helpMessage = ""; var helpTable = "<table width='100%' cellpadding='3px' style='border:1px gray solid;background-color:white;'>"; helpTable += "<tr><th style='background-color:black;color:#fff;padding:5px;'>"; var helpHeader; var helpHeaderClose = "</th></tr>"; var helpTableRowEven = "<tr><td>"; var helpTableRowOdd = "<tr style='background-color:#eee;'><td>"; var helpTableRowClose = "</td></tr>"; var helpTableClose = "</table>"; var makeHelpMessage = function (rows) { helpMessage += helpTable + helpHeader + helpHeaderClose; for (var i = 0; i < rows; i++) { if (i % 2 === 0) { helpMessage += helpTableRowEven; } else { helpMessage += helpTableRowOdd; } helpMessage += lines[i] + helpTableRowClose; } helpMessage += helpTableClose; } if (msg.content.indexOf("!attack") !== -1) { helpHeader = "Attack Type Abbreviations"; lines.push("<b>BA:</b> Blunt Attacks, <b>EA:</b> Edged Attacks, <b>SH:</b> Shooting"); lines.push("<b>TE:</b> Throwing Edged, <b>TB:</b> Throwing Blunt, <b>EN:</b> Energy"); lines.push("<b>FO:</b> Force, <b>GP:</b> Grappling, <b>GB:</b> Grabbing"); lines.push("<b>ES:</b> Escaping, <b>CH:</b> Charging, <b>DO:</b> Dodging"); lines.push("<b>EV:</b> Evading, <b>BL:</b> Blocking, <b>CA:</b> Catching"); lines.push("<b>Stun:</b> Stun?, <b>Slam:</b> Slam?, <b>Kill:</b> Kill?"); makeHelpMessage(6); } if (msg.content.indexOf("!help") !== -1) { helpHeader = "Help"; lines.push("Use the format <b>!ut [rank name] [column shift] [attack type] --roll:[roll name] --id:[character id]<b>"); lines.push("Rank Name must come first, --roll: and --id: options must come at the end"); lines.push("Use <b>!example</b> to get examples on how to use the script"); lines.push("Use <b>!attack</b> for a listing of attack type abbreviation"); makeHelpMessage(4); } if (msg.content.indexOf("!example") !== -1) { helpHeader = "Example"; lines.push("<b>!ut Monstrous</b> (Monstrous rank roll)"); lines.push("<b>!ut Incredible 2 CA</b> (Incredible rank with +2 column shift on a Catching attempt)"); lines.push("<b>!ut Remarkable DO -2</b> (Remarkable rank with -2 column shift on a Dodging attempt)"); lines.push("<b>!ut Excellent BA</b> (Excellent rank Blunt Attack)</td>"); makeHelpMessage(4); } sendChat(msg.who, helpMessage); } /*UNIVERSAL TABLE SCRIPT*/ if(msg.type == "api" && msg.content.indexOf("!ut ") !== -1) { /* OBJECTS AND LISTS */ var rankColumnsObjects = [/*Shift 0*/{startgreen: 66, startyellow: 95, startred: 100}, /*Feeble*/{startgreen: 61, startyellow: 91, startred: 100}, /*Poor*/{startgreen: 56, startyellow: 86, startred: 100}, /*Typical*/{startgreen: 51, startyellow: 81, startred: 98}, /*Good*/{startgreen: 46, startyellow: 76, startred: 98}, /*Excellent*/{startgreen: 41, startyellow: 71, startred: 95}, /*Remarkable*/{startgreen: 36, startyellow: 66, startred: 95}, /*Incredible*/{startgreen: 31, startyellow: 61, startred: 91}, /*Amazing*/{startgreen: 26, startyellow: 56, startred: 91}, /*Monstrous*/{startgreen: 21, startyellow: 51, startred: 86}, /*Unearthly*/{startgreen: 16, startyellow: 46, startred: 86}, /*Shift X*/{startgreen: 11, startyellow: 41, startred: 81}, /*Shift Y*/{startgreen: 7, startyellow: 41, startred: 81}, /*Shift Z*/{startgreen: 4, startyellow: 36, startred: 76}, /*Class 1000*/{startgreen: 2, startyellow: 36, startred: 76}, /*Class 3000*/{startgreen: 2, startyellow: 31, startred: 71}, /*Class 5000*/{startgreen: 2, startyellow: 26, startred: 66}, /*Beyond*/{startgreen: 2, startyellow: 21, startred: 61}]; var rankColumns = ["shift-0", "feeble", "poor", "typical", "good", "excellent", "remarkable", "incredible", "amazing", "monstrous", "unearthly", "shift-x", "shift-y", "shift-z", "class1000", "class3000", "class5000", "beyond"] var attackTypeResults = [/*White*/{ba: "Miss", ea: "Miss", sh: "Miss", te: "Miss", tb: "Miss", en: "Miss", fo: "Miss", gp: "Miss", gb: "Miss", es: "Miss", ch: "Miss", do: "None", ev: "Autohit", bl: "-6 CS", ca: "Autohit", stun: "1-10", slam: "Gr. Slam", kill: "En. Loss"}, /*Green*/{ba: "Hit", ea: "Hit", sh: "Hit", te: "Hit", tb: "Hit", en: "Hit", fo: "Hit", gp: "Miss", gb: "Take", es: "Miss", ch: "Hit", do: "-2 CS", ev: "Evasion", bl: "-4 CS", ca: "Miss", stun: "1", slam: "1 area", kill: "E/S"}, /*Yellow*/{ba: "Slam", ea: "Stun", sh: "Bullseye", te: "Stun", tb: "Hit", en: "Bullseye", fo: "Bullseye", gp: "partial", gb: "Grab", es: "Escape", ch: "Slam", do: "-4 CS", ev: "+1 CS", bl: "-2 CS", ca: "Damage", stun: "No", slam: "Stagger", kill: "No"}, /*Red*/{ba: "Stun", ea: "Kill", sh: "Kill", te: "Kill", tb: "Stun", en: "Kill", fo: "Stun", gp: "Hold", gb: "Break", es: "Reverse", ch: "Stun", do: "-6 CS", ev: "+2 CS", bl: "+1 CS", ca: "Catch", stun: "No", slam: "No", kill: "No"}]; var attackTypes = {ba:"Blunt Attack",ea:"Edged Attack",sh:"Shooting",te:"Throwing Edged",tb:"Throwing Blunt",en:"Energy",fo:"Force",gp:"Grappling",gb:"Grabbing",es:"Escaping",ch:"Charging",do:"Dodging",ev:"Evading",bl:"Blocking",ca:"Catching",stun:"Stun?",slam:"Slam?",kill:"Kill?"}; /* SETUP DICE ROLL AND GET MESSAGE CONTENT */ var rollNum = randomInteger(100); var msgContent = msg.content; var input = msgContent.toLowerCase().split(" "); /* GET INITIAL RANK COLUMN */ var rankCol; /* ALLOW FOR VARIATION IN RANK NAME INPUT */ switch (input[1]) { case "shift0": rankCol = "shift-0"; break; case "0": rankCol = "shift-0"; break; case "fe": rankCol = "feeble"; break; case "pr": rankCol = "poor"; break; case "ty": rankCol = "typical"; break; case "gd": rankCol = "good"; break; case "ex": rankCol = "excellent"; break; case "rm": rankCol = "remarkable"; break; case "in": rankCol = "incredible"; break; case "am": rankCol = "amazing"; break; case "mn": rankCol = "monstrous"; break; case "un": rankCol = "unearthly"; break; case "shiftx": case "x": rankCol = "shift-x"; break; case "shifty": case "y": rankCol = "shift-y"; break; case "shiftz": case "z": rankCol = "shift-z"; break; case "cl1000": case "1000": rankCol = "class1000"; break; case "cl3000": case "3000": rankCol = "class3000"; break; case "cl5000": case "5000": rankCol = "class5000"; break; case "b": rankCol = "beyond"; break; default: rankCol = input[1]; } /* GET INDEX OF INITIAL RANK */ var rankIndex = rankColumns.indexOf(rankCol); /* CHECK THAT RANK NAME IS CORRECT, HALT IF INCORRECT */ if (rankIndex < 0) { sendChat(msg.who, "<i>Rank not found. Please try again.</i>"); //, null, {noarchive: true} } else { /* GET COLUMN SHIFT AND SET ROLL COLUMN */ var colShift; var plus; var rollColumnIndex; _.each(input,function(i){ if (!_.isNaN(parseInt(i))) { colShift = parseInt(i); } else if (i.indexOf("+") > -1) { plus = parseInt(i.replace("+","")); if (!_.isNaN(plus)) { colshift = plus; } } }); if(!colShift) { rollColumnIndex = rankIndex; } else { if (rankIndex + colShift < 0) { rollColumnIndex = 0; } else if (rankIndex + colShift > 17) { rollColumnIndex = 17; } else { rollColumnIndex = rankIndex + colShift; } }; var rollColumn = rankColumnsObjects[rollColumnIndex]; var rollColName = rankColumns[rollColumnIndex].charAt(0).toUpperCase() + rankColumns[rollColumnIndex].slice(1); /* DETERMINE IF THERE IS AN ATTACK TYPE AND RESULT IF THERE IS */ var attackTypeResult; var attackTypeString; var attackTypeRoll; var attackTypeDefault; var attackTypeMarvel; var attack = function(index) { _.each(input,function(i){ if (_.has(attackTypes,i)){ attackTypeResult = attackTypeResults[index][i]; attackTypeRoll = attackTypes[i]; } }); if (attackTypeRoll) { attackTypeDefault = "{{Type=" + attackTypeRoll + "}}"; attackTypeMarvel = "{{attacktype=" + attackTypeRoll + "}}"; attackTypeString = " for a " + attackTypeResult; } else { attackTypeString = ""; } }; /* SEARCH FOR OPTIONAL ROLL TYPE/ID AND ASSIGN TO VARIABLES */ var rollOptionSplit; var rollType; var rollTypeMarvel = ""; var rollTypeDefault = ""; var cid; var character; var charName; var who; if (msgContent.indexOf("--") > -1) { rollOptionSplit = msgContent.split(/\s+--/); _.each(rollOptionSplit,function(string){ if (string.toLowerCase().indexOf("roll:") > -1) { rollType = string.replace(/roll:/i,""); } else if (string.toLowerCase().indexOf("id:") > -1) { cid = string.replace(/id:/i,""); character = getObj("character", cid); if (character !== undefined) { charName = character.get("name"); who = charName; } } }); /* ASSIGN ROLL TYPE FORMATTING */ if (rollType) { rollTypeMarvel = "{{rolltype=" + rollType + "}}"; rollTypeDefault = "<br />" + rollType; } } else { who = msg.who; } /* GATHER RESULTS, FORMAT, AND SEND TO CHAT */ var colorResult; var rollResult; var borderColor; if (rollNum < rollColumn.startgreen) { attack(0); colorResult = "white;\">WHITE"; borderColor = '#FEF68E'; } else if (rollNum >= rollColumn.startgreen && rollNum < rollColumn.startyellow) { attack(1); colorResult = "green;color: white;\">GREEN"; borderColor = 'green'; } else if (rollNum >= rollColumn.startyellow && rollNum < rollColumn.startred) { attack(2); colorResult = "yellow;\">YELLOW"; borderColor = '#FFCC00'; } else if (rollNum >= rollColumn.startred) { attack(3); colorResult = "red;color: white;\">RED"; borderColor = 'red'; }; //Style number result of die roll according to result color rollResult = '<span style="background-color:#FEF68E;border:2px solid ' + borderColor + ';padding:0 3px 0 3px;font-weight:bold;font-size:1.1em;">' + rollNum + '</span>'; /* CHANGE FORMAT OF CHAT RESULT */ var template; var defaultTemplate = "&{template:default} {{name=" + who + rollTypeDefault + "}} " + attackTypeDefault + " {{Column=" + rollColName + "}} {{Roll=" + rollResult + "}}{{Result=<span style=\"padding:2px 5px;font-weight:bold;background-color:" + colorResult + "</span>" + attackTypeString + "}}"; var noTemplate = rollColName + " column: " + rollResult + " is a <span style=\"padding:2px 5px;font-weight:bold; background-color:" + colorResult + "</span> result" + attackTypeString + "."; var marvelTemplate = "&{template:marvel} {{rollname=" + who + "}} " + attackTypeMarvel + " " + rollTypeMarvel + " {{rollcolumn=" + rollColName + "}} {{rollresult=" + rollResult + "}}{{colorresult=<span style=\"padding:2px 5px;font-weight:bold;background-color:" + colorResult + "</span>" + attackTypeString + "}}"; /*********** CHOOSE YOUR TEMPLATE BLOW HERE! ************/ /**** DEFAULT ROLL TEMPLATE ****/ template = defaultTemplate; /**** NO ROLL TEMPLATE ****/ //template = noTemplate; /**** MARVEL THEMED ROLL TEMMPLATE ****/ //template = marvelTemplate /*********** CHOOSE YOUR TEMPLATE ABOVE HERE! ***********/ /*** SENDS FINAL MESSAGE TO CHAT WINDOW - DO NOT CHANGE ***/ sendChat(msg.who, template); }; } });
I have tons of APIs and Macros I use for FASERIP, but nothing so in-depth.  I use a series of macros for my dice rolls that not only add Karma to a roll, but also remove it from the token once that Karma is spent
1655879835

Edited 1655880007
GiGs
Pro
Sheet Author
API Scripter
Those of us who understand scripting probably don't know FASERIP, so it would help if you described - in detail - what you wanted the script to do (like, what is a stun roll, how many dice do you roll and when, etc). Or if you want to understand parts of the script, ask specific questions about it and we can explain.
GiGs said: Those of us who understand scripting probably don't know FASERIP, so it would help if you described - in detail - what you wanted the script to do (like, what is a stun roll, how many dice do you roll and when, etc). Or if you want to understand parts of the script, ask specific questions about it and we can explain. This was to see if there was interest in helping a non-script writer /beginning script writer work on this.  Once I know there are people that will help we can get into specific questions. The first question is getting it to work.  I have looked at the code and it looks like typing "!ut amazing 2 BA" should result in a roll, but it does not.  I sent a PM to Sarah A. to see if she can help me get it to roll some dice.  Once I know how to make it roll (i did a few months ago but RL got in the way) then we can start getting it to run more things. DICE: 1d100 Stun: happens when a certain level of success if rolled for specific type of attack.  So the API would have to know when that type of attack is rolled at that level of success, that a stun roll needs to be rolled. CODE explanation: I figure we will go down section by section and I will state what I think it does (and ask clarifying questions) and once i receive the answers I will add comments to the code as notes. Once that is done I can start trying to add in functionality.
Gallaher said: I have tons of APIs and Macros I use for FASERIP, but nothing so in-depth.  I use a series of macros for my dice rolls that not only add Karma to a roll, but also remove it from the token once that Karma is spent exactly the kind of stuff that I want to add to this.  Sounds like a good project.  I would also like to have damage automatically removed from the token also.
Functionality to add: Bind the API to the attributes in the character sheet (or build macros for the attribute and powers to use the API) so that players do not have to type the command lines. Add in the ability to apply the variables for the API (type of attack, column shifts, etc) via drop lists once a button/token macro (see binding above) has been selected. Add in Karma/hitpoint automation. Other activities may include: creating a new FASERIP character sheet with powers/talents drag and drop creating a compendium supplement that allows drag and drop (if possible) 
Coding Question 1 // UNIVERAL TABLE DICE ROLLER FOR MARVEL SUPER HEROES RPG // Last Update: May, 2015 // Version: 1.0 // Rolls should be structured as "!ut <rank name> <column shift> <attack type> --roll:<roll name> --id:<character id>" // <rank name> must come first after the "!ut" API declaration // <column shift> and <attack type> can come in any order, but must be after the <rank name> and before the options for <roll name> and <character id> // Make sure there are no spaces in the <rank name>. Ex: Shift 0 becomes Shift0 or Shift-0 // --roll:<roll name> can be used to add a title to your roll when using Roll Templates; optional but must come after <rank name>, <column shift>, and <attack type> // --id:<character id> can be used to put the character's name into the Roll Template; optional but must come after <rank name>, <column shift>, and <attack type> //Use !help for reminders on how to use the script //Use !example for examples of how to use the script // Use !attack to print out the list of abbreviations for attack types // TO CHANGE ROLL TEMPLATE: // Scroll down to the bottom of the script // Find the section that says CHOOSE YOUR TEMPLATE HERE! // There are three options each with a header that states what that option is // Each option starts with 'template =' // Remove the // in front of the option you wish to use, the text should turn white // MAKE SURE TO PUT // in front of any options you DO NOT want to use, the text should turn gray // Hit the 'Save Script' button "use strict"; on("chat:message", function(msg) { /* HELPER MESSAGES */ if(msg.type == "api" && (msg.content.indexOf("!attack") !== -1 || msg.content.indexOf("!help") !== -1 || msg.content.indexOf("!example") !== -1)) { var lines = []; var helpMessage = ""; var helpTable = "<table width='100%' cellpadding='3px' style='border:1px gray solid;background-color:white;'>"; helpTable += "<tr><th style='background-color:black;color:#fff;padding:5px;'>"; var helpHeader; var helpHeaderClose = "</th></tr>"; var helpTableRowEven = "<tr><td>"; var helpTableRowOdd = "<tr style='background-color:#eee;'><td>"; var helpTableRowClose = "</td></tr>"; var helpTableClose = "</table>"; var makeHelpMessage = function (rows) { helpMessage += helpTable + helpHeader + helpHeaderClose; for (var i = 0; i < rows; i++) { if (i % 2 === 0) { helpMessage += helpTableRowEven; } else { helpMessage += helpTableRowOdd; } helpMessage += lines[i] + helpTableRowClose; } helpMessage += helpTableClose; } if (msg.content.indexOf("!attack") !== -1) { helpHeader = "Attack Type Abbreviations"; lines.push("<b>BA:</b> Blunt Attacks, <b>EA:</b> Edged Attacks, <b>SH:</b> Shooting"); lines.push("<b>TE:</b> Throwing Edged, <b>TB:</b> Throwing Blunt, <b>EN:</b> Energy"); lines.push("<b>FO:</b> Force, <b>GP:</b> Grappling, <b>GB:</b> Grabbing"); lines.push("<b>ES:</b> Escaping, <b>CH:</b> Charging, <b>DO:</b> Dodging"); lines.push("<b>EV:</b> Evading, <b>BL:</b> Blocking, <b>CA:</b> Catching"); lines.push("<b>Stun:</b> Stun?, <b>Slam:</b> Slam?, <b>Kill:</b> Kill?"); makeHelpMessage(6); } if (msg.content.indexOf("!help") !== -1) { helpHeader = "Help"; lines.push("Use the format <b>!ut [rank name] [column shift] [attack type] --roll:[roll name] --id:[character id]<b>"); lines.push("Rank Name must come first, --roll: and --id: options must come at the end"); lines.push("Use <b>!example</b> to get examples on how to use the script"); lines.push("Use <b>!attack</b> for a listing of attack type abbreviation"); makeHelpMessage(4); } if (msg.content.indexOf("!example") !== -1) { helpHeader = "Example"; lines.push("<b>!ut Monstrous</b> (Monstrous rank roll)"); lines.push("<b>!ut Incredible 2 CA</b> (Incredible rank with +2 column shift on a Catching attempt)"); lines.push("<b>!ut Remarkable DO -2</b> (Remarkable rank with -2 column shift on a Dodging attempt)"); lines.push("<b>!ut Excellent BA</b> (Excellent rank Blunt Attack)</td>"); makeHelpMessage(4); } sendChat(msg.who, helpMessage); } The above is mostly comments and then the help results.  Here are my questions about this secton: what does this mean? "use strict"; I am not sure where to get this from id:[character id]
This section I belive is pretty easy to understand code wise.&nbsp; It sets up the table for results based on the universal chart for FASERIP (<a href="https://majyc.github.io/marvelous/" rel="nofollow">https://majyc.github.io/marvelous/</a>) /*UNIVERSAL TABLE SCRIPT*/ if(msg.type == "api" &amp;&amp; msg.content.indexOf("!ut ") !== -1) { /* OBJECTS AND LISTS */ var rankColumnsObjects = [/*Shift 0*/{startgreen: 66, startyellow: 95, startred: 100}, /*Feeble*/{startgreen: 61, startyellow: 91, startred: 100}, /*Poor*/{startgreen: 56, startyellow: 86, startred: 100}, /*Typical*/{startgreen: 51, startyellow: 81, startred: 98}, /*Good*/{startgreen: 46, startyellow: 76, startred: 98}, /*Excellent*/{startgreen: 41, startyellow: 71, startred: 95}, /*Remarkable*/{startgreen: 36, startyellow: 66, startred: 95}, /*Incredible*/{startgreen: 31, startyellow: 61, startred: 91}, /*Amazing*/{startgreen: 26, startyellow: 56, startred: 91}, /*Monstrous*/{startgreen: 21, startyellow: 51, startred: 86}, /*Unearthly*/{startgreen: 16, startyellow: 46, startred: 86}, /*Shift X*/{startgreen: 11, startyellow: 41, startred: 81}, /*Shift Y*/{startgreen: 7, startyellow: 41, startred: 81}, /*Shift Z*/{startgreen: 4, startyellow: 36, startred: 76}, /*Class 1000*/{startgreen: 2, startyellow: 36, startred: 76}, /*Class 3000*/{startgreen: 2, startyellow: 31, startred: 71}, /*Class 5000*/{startgreen: 2, startyellow: 26, startred: 66}, /*Beyond*/{startgreen: 2, startyellow: 21, startred: 61}]; var rankColumns = ["shift-0", "feeble", "poor", "typical", "good", "excellent", "remarkable", "incredible", "amazing", "monstrous", "unearthly", "shift-x", "shift-y", "shift-z", "class1000", "class3000", "class5000", "beyond"] var attackTypeResults = [/*White*/{ba: "Miss", ea: "Miss", sh: "Miss", te: "Miss", tb: "Miss", en: "Miss", fo: "Miss", gp: "Miss", gb: "Miss", es: "Miss", ch: "Miss", do: "None", ev: "Autohit", bl: "-6 CS", ca: "Autohit", stun: "1-10", slam: "Gr. Slam", kill: "En. Loss"}, /*Green*/{ba: "Hit", ea: "Hit", sh: "Hit", te: "Hit", tb: "Hit", en: "Hit", fo: "Hit", gp: "Miss", gb: "Take", es: "Miss", ch: "Hit", do: "-2 CS", ev: "Evasion", bl: "-4 CS", ca: "Miss", stun: "1", slam: "1 area", kill: "E/S"}, /*Yellow*/{ba: "Slam", ea: "Stun", sh: "Bullseye", te: "Stun", tb: "Hit", en: "Bullseye", fo: "Bullseye", gp: "partial", gb: "Grab", es: "Escape", ch: "Slam", do: "-4 CS", ev: "+1 CS", bl: "-2 CS", ca: "Damage", stun: "No", slam: "Stagger", kill: "No"}, /*Red*/{ba: "Stun", ea: "Kill", sh: "Kill", te: "Kill", tb: "Stun", en: "Kill", fo: "Stun", gp: "Hold", gb: "Break", es: "Reverse", ch: "Stun", do: "-6 CS", ev: "+2 CS", bl: "+1 CS", ca: "Catch", stun: "No", slam: "No", kill: "No"}]; var attackTypes = {ba:"Blunt Attack",ea:"Edged Attack",sh:"Shooting",te:"Throwing Edged",tb:"Throwing Blunt",en:"Energy",fo:"Force",gp:"Grappling",gb:"Grabbing",es:"Escaping",ch:"Charging",do:"Dodging",ev:"Evading",bl:"Blocking",ca:"Catching",stun:"Stun?",slam:"Slam?",kill:"Kill?"}; /* SETUP DICE ROLL AND GET MESSAGE CONTENT */ var rollNum = randomInteger(100); var msgContent = msg.content; var input = msgContent.toLowerCase().split(" "); /* GET INITIAL RANK COLUMN */ var rankCol; /* ALLOW FOR VARIATION IN RANK NAME INPUT */ switch (input[1]) { case "shift0": rankCol = "shift-0"; break; case "0": rankCol = "shift-0"; break; case "fe": rankCol = "feeble"; break; case "pr": rankCol = "poor"; break; case "ty": rankCol = "typical"; break; case "gd": rankCol = "good"; break; case "ex": rankCol = "excellent"; break; case "rm": rankCol = "remarkable"; break; case "in": rankCol = "incredible"; break; case "am": rankCol = "amazing"; break; case "mn": rankCol = "monstrous"; break; case "un": rankCol = "unearthly"; break; case "shiftx": case "x": rankCol = "shift-x"; break; case "shifty": case "y": rankCol = "shift-y"; break; case "shiftz": case "z": rankCol = "shift-z"; break; case "cl1000": case "1000": rankCol = "class1000"; break; case "cl3000": case "3000": rankCol = "class3000"; break; case "cl5000": case "5000": rankCol = "class5000"; break; case "b": rankCol = "beyond"; break; default: rankCol = input[1]; } /* GET INDEX OF INITIAL RANK */ var rankIndex = rankColumns.indexOf(rankCol);
Gallaher said: I have tons of APIs and Macros I use for FASERIP, but nothing so in-depth.&nbsp; I use a series of macros for my dice rolls that not only add Karma to a roll, but also remove it from the token once that Karma is spent Can you point me towards more macros/API for FASERIP?
This section has some activity that I cannot decipher&nbsp; /* CHECK THAT RANK NAME IS CORRECT, HALT IF INCORRECT */ if (rankIndex &lt; 0) { sendChat(msg.who, "&lt;i&gt;Rank not found. Please try again.&lt;/i&gt;"); //, null, {noarchive: true} } else { /* GET COLUMN SHIFT AND SET ROLL COLUMN */ var colShift; var plus; var rollColumnIndex; _.each(input,function(i){ if (!_.isNaN(parseInt(i))) { colShift = parseInt(i); } else if (i.indexOf("+") &gt; -1) { plus = parseInt(i.replace("+","")); if (!_.isNaN(plus)) { colshift = plus; } } }); if(!colShift) { rollColumnIndex = rankIndex; } else { if (rankIndex + colShift &lt; 0) { rollColumnIndex = 0; } else if (rankIndex + colShift &gt; 17) { rollColumnIndex = 17; } else { rollColumnIndex = rankIndex + colShift; } }; var rollColumn = rankColumnsObjects[rollColumnIndex]; var rollColName = rankColumns[rollColumnIndex].charAt(0).toUpperCase() + rankColumns[rollColumnIndex].slice(1); Specifically: _.each(input,function(i){ if (!_.isNaN(parseInt(i))) { colShift = parseInt(i); } else if (i.indexOf("+") &gt; -1) { plus = parseInt(i.replace("+","")); if (!_.isNaN(plus)) { colshift = plus; so this looks like it is picking up an integer variable to use for the column shift.&nbsp; I am just not following how it does it.
1655898976
timmaugh
Pro
API Scripter
Hey, RC... I can help a little bit. use strict "use strict" is a statement that changes how javascript is interpreted and optimized by browsers. Think of it like an "opt-in" that limits your code to a subset of javascript features that make js more reliable. (I use that word very loosely). For instance, "use strict" changes javascript to announce some errors that would otherwise be silent. Strict mode is enacted by default on arrow functions: let thisFunctionIsStrict = () =&gt; { &nbsp; console.log("Hey, look at me. I'm strict!"); } id:[character id] This is in the example text provided by the original script author. She is asking you to fill in the character id to the id argument in the command line. There is a fairly ubiquitous convention in Roll20 scripts that command line arguments are separated by double-hyphens: !scripthandle --arg1 --arg2 Then there is typically an arg/value syntax that allows you to supply a value to be used. In this case, the script author has you separate the arg name (id) from the value you would provide (the character's id) with a colon. --id:-M1234567890abcdef --id:@{selected|token_id} ...etc... Note, Roll20 does some sanitation in different ways in different places. One place it sanitizes is around command lines used as chat buttons -- colons are interpreted as hyperlinks, and your button won't work. So, if you are going to continue with the script, you might look at changing the delimiter between arg &amp; value. Again, convention suggests you use either/or of a hash (#) or a pipe (|) character for this separation. *...goes back to read more...*
1655900840
timmaugh
Pro
API Scripter
rcbricker said: This section has some activity that I cannot decipher: &lt;&lt;SNIP&gt;&gt; Specifically: _.each(input,function(i){ if (!_.isNaN(parseInt(i))) { colShift = parseInt(i); } else if (i.indexOf("+") &gt; -1) { plus = parseInt(i.replace("+","")); if (!_.isNaN(plus)) { colshift = plus; so this looks like it is picking up an integer variable to use for the column shift.&nbsp; I am just not following how it does it. So, this is using the underscore library (available to you on Roll20), to get the _.each() function ( documentation ). Given developments in javascript over the past several years, the underscore library is less and less necessary -- most everything it can do for you these days can be done with standard js. In this case, .forEach() . For both _.each() and .forEach(), you are operating over an array (or other iterable data). In the underscore version, you have to provide the list (the array) as the first argument (called input , above). Then the next argument represents what you will do with it when you have it (a callback function... function (i){...} ). The i parameter of the function is how you will refer to each individual element of the list you pass into input . In other words, with each iteration of your data, you will refer to the iteration data as i . What the callback does Here is an annotated version of the function: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.each(input,function(i){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!_.isNaN(parseInt(i))) { // see if i will parse as an integer, if it is not Not-A-Number (ie, it is a number)... &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; colShift = parseInt(i);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // assign the parsed integer to the colShift variable &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (i.indexOf("+") &gt; -1) { // otherwise, see if there is a + character in i (as if it was +2) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; plus = parseInt(i.replace("+","")); // if there is, replace it &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!_.isNaN(plus)) { // test if the result of that operation is not Not-A-Number &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; colshift = plus; // if so, assign the parsed integer to the colShift variable &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); That can all be simplified since most of that can be chained, one to the next. You also don't need the&nbsp; underscore library as there is a modern js version for both _.each and _.isNaN . If I knew I was going to be getting an array of strings, I would probably write that function as: input.forEach(i =&gt; { &nbsp; if (isNaN(parseInt(i.replace('+','')))) colshift = parseInt(i.replace('+','')); }); ...but now I'll have to look at the original code, since there would have to be a good reason why an iterative function (running n-times over data) would set a single variable with a higher scope. The colshift variable will always be set to the last value generated by the iterative function. Maybe that's what is wanted, but I'd have to be sure.
1655901636

Edited 1655904339
timmaugh
Pro
API Scripter
One other point is that, because of the way it is built, this script won't always work with metascripts. If you don't want to convert it to the revealing module pattern (Aaron has a newer template for that linked a few times on this forum), you can simply wrap your code in an on('ready',...) In other words, turn this: on('chat:message', function(msg){ &nbsp; &nbsp; // ... }); ...into... on('ready', function(){ &nbsp; &nbsp; on('chat:message', function(msg) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; // ... &nbsp; &nbsp; }); }); That won't change the behavior of this script, but it will allow for metascript interaction with it... should a user ever want to make use of the metascript toolbox.
timmaugh said: One other point is that, because of the was it is built, this script won't always work with metascripts. If you don't want to convert it to the revealing module pattern (Aaron has a newer template for that linked a few times on this forum), you can simply wrap your code in an on('ready',...) In other words, turn this: on('chat:message', function(msg){ &nbsp; &nbsp; // ... }); ...into... on('ready', function(){ &nbsp; &nbsp; on('chat:message', function(msg) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; // ... &nbsp; &nbsp; }); }); That won't change the behavior of this script, but it will allow for metascript interaction with it... should a user ever want to make use of the metascript toolbox. ok that was a lot to digest.&nbsp; I have learned that I am way out of my league here.&nbsp; Is there a way to hire someone to do what I want? &nbsp; I can walk them through the FASERIP concepts so we can develop a viable script.&nbsp; If I try to do this, it is gonna take awhile to learn enough JS before i can even begin.&nbsp; I don't mind learning JS but I want this sooner than it will take for me to spin up on all this.
1655921374

Edited 1655921457
GiGs
Pro
Sheet Author
API Scripter
rcbricker said: ok that was a lot to digest.&nbsp; I have learned that I am way out of my league here.&nbsp; Is there a way to hire someone to do what I want? &nbsp; I can walk them through the FASERIP concepts so we can develop a viable script.&nbsp; If I try to do this, it is gonna take awhile to learn enough JS before i can even begin.&nbsp; I don't mind learning JS but I want this sooner than it will take for me to spin up on all this. Firstly, I would suggest ignoring everything Tim has said. (Sorry, Tim.). Genearly speaking, when absolute newbies come here for help, experts forget they are dealing with newbies and are really bad at tailoring their guidance for newbies.That said, jumping in to figure out a complete script can be difficult, but it's not impossible. Here's how I would answer your questions: use strict; You don't need to know this, for now. Just include it and forget about it. --id:[character id] Every character in a roll20 campaign has a character id. You can get it in numberous ways, like @{bob|character_id} or @{selected|character_id}. This line is telling you that you need to supply an id for each character you want to use the script on. So, imagine you have a character called Bob who is making an energy attack. You'd look at the command instructions: !ut &lt;rank name&gt; &lt;column shift&gt; &lt;attack type&gt; --roll:&lt;roll name&gt; --id:&lt;character id&gt; And from there build an attack macro that maybe looks like this: !ut Excellent shift-0 EN --roll:Bob's Energy Attack! --id:@{Bob|character_id} (I'm not familiar with the game, I got the Excellent, shift-0 and EN from looking at the code.) You could change that macro to include dropdowns to let you pick attack ranks, column shifts and attack types, or you might get those values from a character sheet with things like @{bob|Attack-Type}. I don't know how the character sheet is built - the attribute name you'd use would be different. It's important to make sure everything listed above has a valid value - --roll: is the only part that can include your own text, and that iwll be used as the visible label for the output. So to answer more specifically: you get the id from the macro command, like the example above. Roll20 sees @{bob|character_id} and grabs the correct id for you. Your final question: rcbricker said: This section has some activity that I cannot decipher&nbsp; /* CHECK THAT RANK NAME IS CORRECT, HALT IF INCORRECT */ if (rankIndex &lt; 0) { sendChat(msg.who, "&lt;i&gt;Rank not found. Please try again.&lt;/i&gt;"); //, null, {noarchive: true} } else { /* GET COLUMN SHIFT AND SET ROLL COLUMN */ var colShift; var plus; var rollColumnIndex; _.each(input,function(i){ if (!_.isNaN(parseInt(i))) { colShift = parseInt(i); } else if (i.indexOf("+") &gt; -1) { plus = parseInt(i.replace("+","")); if (!_.isNaN(plus)) { colshift = plus; } } }); if(!colShift) { rollColumnIndex = rankIndex; } else { if (rankIndex + colShift &lt; 0) { rollColumnIndex = 0; } else if (rankIndex + colShift &gt; 17) { rollColumnIndex = 17; } else { rollColumnIndex = rankIndex + colShift; } }; var rollColumn = rankColumnsObjects[rollColumnIndex]; var rollColName = rankColumns[rollColumnIndex].charAt(0).toUpperCase() + rankColumns[rollColumnIndex].slice(1); Specifically: _.each(input,function(i){ if (!_.isNaN(parseInt(i))) { colShift = parseInt(i); } else if (i.indexOf("+") &gt; -1) { plus = parseInt(i.replace("+","")); if (!_.isNaN(plus)) { colshift = plus; so this looks like it is picking up an integer variable to use for the column shift.&nbsp; I am just not following how it does it. That is very complex code, and it's easy to get confused by it. But here's some psuedo code to tell you what it is doing: With each item in the input list If the item is a number: set the variable colShift to equal that number ELSE if the item is not a number take i, and if it has a +, it with "" (remove the + in other words) If the altered I is NOW a number set the colShift variable to that number. input is a list of values created elsewhere in the script. The code above is testing each value in the list, one by one, seeing if any of them are a number, and if they are, assigning that number value to colShift. If none of them are a number, it ignores them. Now this script could have been constructed differently to make this part of the script much simpler (or eliminate it completely). But that's what it's doing. I can break it down and tell you how it;s doing it if you like, but it will make your head hurt.
1655922392

Edited 1655924551
GiGs
Pro
Sheet Author
API Scripter
rcbricker said: Functionality to add: Bind the API to the attributes in the character sheet (or build macros for the attribute and powers to use the API) so that players do not have to type the command lines. Add in the ability to apply the variables for the API (type of attack, column shifts, etc) via drop lists once a button/token macro (see binding above) has been selected. Add in Karma/hitpoint automation. Other activities may include: creating a new FASERIP character sheet with powers/talents drag and drop creating a compendium supplement that allows drag and drop (if possible)&nbsp; The first 2 points here can be done without altering the script. Look into roll20 Macros, and dropdown queries. You can create a macro that gets all the needed values and players never enter any code. They just click a button, maybe pick a couple of values from dropdowns and the script runs. I'd show examples, but I don't know the system. Adding hit point values, etc., would require an edit to the script. For the Other Activities, I have bad news: building a character sheet is a huge project, likely taking months of work. There is no way to build a custom compendium - only a licensed partner can build those.
GiGs said: rcbricker said: Functionality to add: Bind the API to the attributes in the character sheet (or build macros for the attribute and powers to use the API) so that players do not have to type the command lines. Add in the ability to apply the variables for the API (type of attack, column shifts, etc) via drop lists once a button/token macro (see binding above) has been selected. Add in Karma/hitpoint automation. Other activities may include: creating a new FASERIP character sheet with powers/talents drag and drop creating a compendium supplement that allows drag and drop (if possible)&nbsp; The first 2 points here can be done without altering the script. Look into roll20 Macros, and dropdown queries. You can create a macro that gets all the needed values and players never enter any code. They just click a button, maybe pick a couple of values from dropdowns and the script runs. I'd show examples, but I don't know the system. Adding hit point values, etc., would require an edit to the script. For the Other Activities, I have bad news: building a character sheet is a huge project, likely taking months of work. There is no way to build a custom compendium - only a licensed partner can build those. Thanks for all this.&nbsp; It seems easier to chew after your explanation. I understand how one can forget that you are talking to a noob, i run into this in my area of expertise.&nbsp; I will definitely like to review this code to see if it can be streamlined and automate the variables.&nbsp; I can look into some of the macros I use for D&amp;D5e and canablize them to see if they will fit here.&nbsp; Stupid compendium rules lol.&nbsp; The current sheet might work if I can get Macros to replace the attribute buttons so that they use the API. IF I bring my knowledge of the ruleset (cause I got the books for things i don't remember) and if you all are willing to help with my JS questions, I think we can get this to work.&nbsp; YAY! Seriously, Thanks guys.
1655924508
GiGs
Pro
Sheet Author
API Scripter
If you are using the existing sheet for your own purpose, it should be fairly easy to modify the buttons to use script calls. It's not a good idea to update the public sheet with API script features unless they are strictly optional, because most people don't have access to the API and that would render the sheet useless for them.
1655924750
GiGs
Pro
Sheet Author
API Scripter
GiGs said: Add in Karma/hitpoint automation. I mentioned this earlier as requiring a change to the script. I don't know the sheet or script output, but this might not require a big change to the script: you might be able to get away with just tweaking it to so it would work with Tim's meta scripts. Tim has a bunch of scripts that can work with other scripts in clever ways. Tim mentioned Aaron's script template - it wouldn't be hard to modify the script to use that.
1655925125

Edited 1655932275
GiGs
Pro
Sheet Author
API Scripter
timmaugh said: ...but now I'll have to look at the original code, since there would have to be a good reason why an iterative function (running n-times over data) would set a single variable with a higher scope. The colshift variable will always be set to the last value generated by the iterative function. Maybe that's what is wanted, but I'd have to be sure. The way the script is written, it's doing very little to sanitise input, and is expecting there to be either no numbers, or exactly one number. There's a value, column shift, which seems to have values that can be valid as numbers or as text like "shift-0". The code is basucally looking at the message content here, to see if there's a number in there, in format "7" or "+7", and if so it is assuming that number if a valid column shift value. And if not, it lets another part of the script find the valid value. So its basically doing primitive analysis of msg.content, to try to find a number and automatically assigning that to column shift if there is one. The script would be better off demanding an explicit column shift parameter, and this part of the code would be unneccessary.
GiGs said: timmaugh said: ...but now I'll have to look at the original code, since there would have to be a good reason why an iterative function (running n-times over data) would set a single variable with a higher scope. The colshift variable will always be set to the last value generated by the iterative function. Maybe that's what is wanted, but I'd have to be sure. The way the script is written, it's doing very little to sanitise input. There's a value, column shift, which seems to have values that ccan be valid as numbers or as stect like "shift-0". The code is basucally looking at the message content here, to see if there's a number in there, in format "7" or "+7", and if so it is assuming that number if a valid column shift value. And if not, it lets another part of the script find the valid value. So its basically doing primitive analysis of msg.content, to try to find a number and automatically assigning that to column shift if there is one. The script would be off demanding an explicit column shift parameter, annd this part of the code would be unnecessary. I would like the script to look at the underlying actions attribute for the rankcolumn if possible or the power/talent's rankcolumn.&nbsp; this might be something that needs to be added to the macro's as the I believe the sheet uses text only entries for powers and talents.&nbsp; I'll have to look closer to those areas.&nbsp; I would like powers to work like spells in D&amp;D5e where there are fields that you can modify/ pull data from for macros.&nbsp; if we can find away to add powers into the sheet that a macro can read attributes from then we should be able to build simple token actions for power attacks and talent checks. quick combat tut for FASERIP: column shifts would/can be added via dropdown for the token action as they are a resistance check against the incoming action.&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;I want to hit you, so you roll your agility to dodge that creates a column shift that affects my fighting attempt so I would select fighting to I can punch you and the drop down asks for column shift.&nbsp; I select it from the dropdown and that affects which column is checked for my hit to succeed.&nbsp; Would need an input box to add karma (which would be removed from the token/sheet).
OK lets talk about what this does.&nbsp; this script does a good job of posting to chat the roll and its results compared against the advanced universal table.&nbsp; The Universal table (UT) is chart made of columns.&nbsp; One column for each power ranking.&nbsp; there are 17 (if i remember correctly) and all actions are compared to it has a means to resolve conflict.&nbsp; want to open a stuck window?&nbsp; roll (1d100) your strength using the column rank of your strength attribute.&nbsp; The results are presented as a color.&nbsp; White is generally a failure, Green a minor success, Yellow a decent success and Red and significant success.&nbsp; These are general as in certain situations they are inverted and results are degrees of failure. The Advanced FASERIP system introduced the advanced Universal table.&nbsp; This added across the top of the chart specific types of checks based on combat actions and saves.&nbsp; These actions showed extended results such as the possibility of slamming one's opponent due to a yellow success of a charge attack.&nbsp;&nbsp; This is a down and dirty explanation of the game.&nbsp; This game is a non-leveling superhero game designed to be played in the Marvel Universe.&nbsp; Characters do not advance or improve through levels. Karma is awarded for completing adventures, good role-playing, doing good deeds, etc.&nbsp; &nbsp; Progress is achieved by spending Karma Points to increase ones attributes, acquire new powers, new power stunts (new types of uses of existing powers), new talents, etc. Conflict is resolved by one or both combatants rolling d100 and comparing the result(s) to the UT.&nbsp; Players and NPCs can spend Karma to affect their roll on a 1:1 basis.&nbsp; the defender's results are calculated first and any affect is applied to the attacker's action as a modifier.&nbsp; Any Karma declaration must be before action are resolved.&nbsp; Karma is spent once declared regardless of the results. example of play: &nbsp; &nbsp; Attacker - I want to hit defender in his smug goody two shoes face &nbsp; &nbsp; Defender - I'm gonna dodge &nbsp; &nbsp; Defender rolls 1d100 and gets a 42.&nbsp; He checks the UT for his Excellent Agility and sees that it is a Green result meaning a -2 Column Shift (CS) to Attacker's roll &nbsp; &nbsp; Attacker rolls 1d100 and gets a 45.&nbsp; He checks the UT for his Remarkable fighting and sees that it is a Green result meaning a a hit.&nbsp; However he needs to move left on the UT down to the Good Rank column due to Defender's result.&nbsp; Now a 45 results in a white or miss.&nbsp; Attacker could have spent Karma to increase his chances to hit Defender.&nbsp;
So i think it would be good for me and the project to start from scratch.&nbsp; I have almost no experice with JS.&nbsp; My base is VBA (be gentle).&nbsp; What the hell.&nbsp; Good place to learn.
1655930052
GiGs
Pro
Sheet Author
API Scripter
You'll need to show what that UT looks like. A programmer will need to know how to value individual rolls.
1655930303

Edited 1655988589
Here is what I think we need this script to do since the character sheet is not overly automated. Convert the Advance Universal Table (AUT) into code for conflict resolution. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nest result conflict resolution (endurance save to keep from being stunned) into the overall conflict resolution Check the character sheet (CHS) for the correct power/attribute/talent ranking as needed to perform the action&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; After checking the CHS the power section, each power has five fields.&nbsp; Need to determine what those fields are so that we can tap them for power rankings for the rolls. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; or make the power/attribute/talent clickable &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; Find out who to check with for making changes to the CHS drop down (either here or in macro from sheet or token) to spend Karma &nbsp; &nbsp; subtract Karma from token/sheet drop down (either here or in macro from sheet or token) to adjust for Column Shifts (CS) roll 1d100&nbsp; &nbsp; &nbsp; Apply Karma if any spent compare die roll to Advance Universal Table (AUT) &nbsp; &nbsp; Adjust for CSs if any &nbsp; &nbsp; capture result as a variable &nbsp; &nbsp; Check if result has a nested check for extended result (ie stun) &nbsp; &nbsp; roll 1d100 for extended results, if any &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; capture result as a variable present result in chat &nbsp; &nbsp; die roll &nbsp; &nbsp; action result color &nbsp; &nbsp; action result text apply hit point change to token/sheet That is what I have right now.&nbsp; I will edit this as I think of other things.
GiGs said: You'll need to show what that UT looks like. A programmer will need to know how to value individual rolls. I can provide the chart.&nbsp; Sarah A.'s code has it explained pretty well but I will attach/post it here once i figure out how to do it.
So what would be the best way to set up the chart?&nbsp; We want to identify the color ranges per column.&nbsp; We also want to identify the extend results for the actions across the top.&nbsp;&nbsp;
ok so I thought about the flow of the combat and that should dictate the flow of how the roll is developed.&nbsp;&nbsp; from a player's point of view: click token action macro &nbsp;&nbsp;&nbsp;&nbsp;select attack type &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the rank column variable by checking the character sheet for the attribute &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;default range of white is set &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the range for green &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the range for yellow &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the range for red &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the attack type variable &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the results of white variable &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the results of Green variable &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the results of yellow variable &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this will set the results of red variable &nbsp; &nbsp; New input box ask player for Karma &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; Karma reduced on token/sheet &nbsp; &nbsp; New drop down asks for column shift&nbsp; Dice rolled results applied to target So here is the path forward. //create drop down and populate it with list of combat actions // Example: BA (blunt attack), EA (Energy Attack) //set combat action to variable // Example: strCA=BA //create a switch to set variables for combat actions // white results // green results // yellow results // red results // Example: strCA combat results - strW= "Miss", strG= "Hit", strY= "Slam", strR= "Stun" //check character sheet for base attribute ranking //set ranking to variable // Example: strCA base attribute rank - strRANK = "Fighting" //create switch to check for rank column variables //each entry in switch should apply the following: //white less than or equal to //Green starting and ending //Yellow starting and ending //Red greater than or equals to // Example: // switch // case Excellent // intW = 40 // intG = 41 - 70 (Probably going to need two variables intGh = 70, intGl= 41 something like that // intY = 71 - 94 (see above) // intR = 95 //input box to get karma (input box for typing amount) // check character sheet to ensure enough karma is available // input box to ask for karma again with error message "lack of Karma" from previous ask/check or default to remaining Karma and ask if the player agrees // If character does not agree then input box to get karma again //drop down to get the column shift // ask for target //roll random (100) //reduce karma //send dice results to chat //apply damage to target
Hi folks, original scripter here. First off, I'd like to preemptively apologize for any faulty work in the script since it was my first foray into javascript and that was quite a few years ago now. I'm sorry if it's incomprehensible garbage! Second, I did create a character sheet based off the existing one that works with the API. If you'd like to tear it apart and use anything useful, you can find it all here:&nbsp; <a href="https://github.com/madartiste/roll20-character-sheets/tree/master/MarvelSH_API" rel="nofollow">https://github.com/madartiste/roll20-character-sheets/tree/master/MarvelSH_API</a>
Thanks Sarah.&nbsp; It is a great API.&nbsp; I just want to add some stuff to it.&nbsp; It has been suggested that it would be better for my learning to start from scratch.&nbsp; I don't want to completely re-invent the wheel, so I am using your script as a concept.&nbsp;&nbsp;
1656085762
timmaugh
Pro
API Scripter
rcbricker said: So what would be the best way to set up the chart?&nbsp; We want to identify the color ranges per column.&nbsp; We also want to identify the extend results for the actions across the top.&nbsp;&nbsp; Look at how Sarah did it for a start... she established "breakpoints" for each color in each column: var rankColumnsObjects = [/*Shift 0*/{startgreen: 66, startyellow: 95, startred: 100}, /*Feeble*/{startgreen: 61, startyellow: 91, startred: 100}, /*Poor*/{startgreen: 56, startyellow: 86, startred: 100}, /*Typical*/{startgreen: 51, startyellow: 81, startred: 98}, /*Good*/{startgreen: 46, startyellow: 76, startred: 98}, /*Excellent*/{startgreen: 41, startyellow: 71, startred: 95}, /*Remarkable*/{startgreen: 36, startyellow: 66, startred: 95}, /*Incredible*/{startgreen: 31, startyellow: 61, startred: 91}, /*Amazing*/{startgreen: 26, startyellow: 56, startred: 91}, /*Monstrous*/{startgreen: 21, startyellow: 51, startred: 86}, /*Unearthly*/{startgreen: 16, startyellow: 46, startred: 86}, /*Shift X*/{startgreen: 11, startyellow: 41, startred: 81}, /*Shift Y*/{startgreen: 7, startyellow: 41, startred: 81}, /*Shift Z*/{startgreen: 4, startyellow: 36, startred: 76}, /*Class 1000*/{startgreen: 2, startyellow: 36, startred: 76}, /*Class 3000*/{startgreen: 2, startyellow: 31, startred: 71}, /*Class 5000*/{startgreen: 2, startyellow: 26, startred: 66}, /*Beyond*/{startgreen: 2, startyellow: 21, startred: 61}]; var rankColumns = ["shift-0", "feeble", "poor", "typical", "good", "excellent", "remarkable", "incredible", "amazing", "monstrous", "unearthly", "shift-x", "shift-y", "shift-z", "class1000", "class3000", "class5000", "beyond"] var attackTypeResults = [/*White*/{ba: "Miss", ea: "Miss", sh: "Miss", te: "Miss", tb: "Miss", en: "Miss", fo: "Miss", gp: "Miss", gb: "Miss", es: "Miss", ch: "Miss", do: "None", ev: "Autohit", bl:&nbsp; "-6 CS", ca: "Autohit", stun: "1-10", slam: "Gr. Slam", kill: "En. Loss"}, /*Green*/{ba: "Hit", ea: "Hit", sh: "Hit", te: "Hit", tb: "Hit", en: "Hit", fo: "Hit", gp: "Miss", gb: "Take", es: "Miss", ch: "Hit", do: "-2 CS", ev: "Evasion", bl: "-4 CS", ca: "Miss", stun: "1", slam: "1 area", kill: "E/S"}, /*Yellow*/{ba: "Slam", ea: "Stun", sh: "Bullseye", te: "Stun", tb: "Hit", en: "Bullseye", fo: "Bullseye", gp: "partial", gb: "Grab", es: "Escape", ch: "Slam", do: "-4 CS", ev: "+1 CS", bl: "-2 CS", ca: "Damage", stun: "No", slam: "Stagger", kill: "No"}, /*Red*/{ba: "Stun", ea: "Kill", sh: "Kill", te: "Kill", tb: "Stun", en: "Kill", fo: "Stun", gp: "Hold", gb: "Break", es: "Reverse", ch: "Stun", do: "-6 CS", ev: "+2 CS", bl: "+1 CS", ca: "Catch", stun: "No", slam: "No", kill: "No"}]; var attackTypes = {ba:"Blunt Attack",ea:"Edged Attack",sh:"Shooting",te:"Throwing Edged",tb:"Throwing Blunt",en:"Energy",fo:"Force",gp:"Grappling",gb:"Grabbing",es:"Escaping",ch:"Charging",do:"Dodging",ev:"Evading",bl:"Blocking",ca:"Catching",stun:"Stun?",slam:"Slam?",kill:"Kill?"}; For instance, the first one (rankColumnsObjects) is an array to represent the main body of the table (Shift 0, Feeble, Poor, etc.). This is good because you can use the column shift (a number) with the index of the array to find the appropriate breakpoint. If you look in the code, you'll probably see where she uses resolution of one of these object's properties to extract a piece of data from another object. See if the logic works for you. I'd have to think about it a bit, but I think I would structure the attackTypeResults as an object (instead of an array), with keys of the result colors: const attackTypeResults = { &nbsp; &nbsp; white: { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; ba: 'Miss', &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; ea: 'Miss', &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; // ... &nbsp; &nbsp; }, &nbsp; &nbsp; green: { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; ba: // ... &nbsp; &nbsp; } } And then I could derive the attack type and the color result of the attack roll and reference it quickly: attackTypeResults[resultColor][attackType]
I was thinking that it would be best to treat the chart as a table.&nbsp; I would like the API to get the ranking from the sheet and the attack type.&nbsp; Then when it rolls the dice it is able to assign color and then attack type (based on color) results.
1656086915
timmaugh
Pro
API Scripter
Ultimately, that's what would be happening with this structure: attackTypeResults[resultColor][attackType] ↳ table ↳ row ↳ column
timmaugh said: Ultimately, that's what would be happening with this structure: attackTypeResults[resultColor][attackType] ↳ table ↳ row ↳ column Could you do the same thing with the die results?&nbsp;&nbsp; dieResults[dieroll][rankcolumn] ↳ table ↳ row ↳ column
So I think you are suggesting this or something close: var rankColumns = ["shift-0", "feeble", "poor", "typical", "good", "excellent", "remarkable", "incredible", "amazing", "monstrous", "unearthly", "shift-x", "shift-y", "shift-z", "class1000", "class3000", "class5000", "beyond"] var attackTypes = {ba:"Blunt Attack",ea:"Edged Attack",sh:"Shooting",te:"Throwing Edged",tb:"Throwing Blunt",en:"Energy",fo:"Force",gp:"Grappling",gb:"Grabbing",es:"Escaping",ch:"Charging",do:"Dodging",ev:"Evading",bl:"Blocking",ca:"Catching",stun:"Stun?",slam:"Slam?",kill:"Kill?"}; const RankColumnObjects = { shift-0: { startG: '66', startY: '95', startR: '100' }, feeble: { startG: '61', startY: '91', startR: '100' } poor: { startG: '56', startY: '86', startR: '100' } typical: { startG: '51', startY: '81', startR: '98' } good: { startG: '46', startY: '75', startR: '98' } excellent: { startG: '41', startY: '71', startR: '95' } remarkable: { startG: '36', startY: '66', startR: '95' } incredible: { startG: '31', startY: '61', startR: '91' } amazing: { startG: '26', startY: '56', startR: '91' } monstrous: { startG: '21', startY: '51', startR: '86' } unearthly: { startG: '16', startY: '46', startR: '86' } shift-X: { startG: '11', startY: '41', startR: '81' } shift-Y: { startG: '07', startY: '41', startR: '81' } shift-Z: { startG: '04', startY: '36', startR: '75' } class1000: { startG: '02', startY: '36', startR: '75' } class3000: { startG: '02', startY: '31', startR: '71' } class5000: { startG: '02', startY: '26', startR: '66' } beyond: { startG: '02', startY: '21', startR: '61' } }; const attackTypeResults = { white: { ba: 'Miss', ea: 'Miss', sh: "Miss" te: "Miss" tb: "Miss" en: "Miss" fo: "Miss" gp: "Miss" gb: "Miss" es: "Miss" ch: "Miss" do: "None" ev: "Autohit" bl: "-6 CS" ca: "Autohit" stun: "1-10" slam: "Gr. Slam" kill: "En. Loss"} }, green: { ba: "Hit" ea: "Hit" sh: "Hit" te: "Hit" tb: "Hit" en: "Hit" fo: "Hit" gp: "Miss" gb: "Take" es: "Miss" ch: "Hit" do: "-2 CS" ev: "Evasion" bl: "-4 CS" ca: "Miss" stun: "1" slam: "1 area" kill: "E/S" } Yellow: { ba: "Slam" ea: "Stun" sh: "Bullseye" te: "Stun" tb: "Hit" en: "Bullseye" fo: "Bullseye" gp: "partial" gb: "Grab" es: "Escape" ch: "Slam" do: "-4 CS" ev: "+1 CS" bl: "-2 CS" ca: "Damage" stun: "No" slam: "Stagger" kill: "No" } Red: { ba: "Stun" ea: "Kill" sh: "Kill" te: "Kill" tb: "Stun" en: "Kill" fo: "Stun" gp: "Hold" gb: "Break" es: "Reverse" ch: "Stun" do: "-6 CS" ev: "+2 CS" bl: "+1 CS" ca: "Catch" stun: "No" slam: "No" kill: "No" } }
in the below code.&nbsp; the first three variables are defined using square brackets [ ], but the last is using curly brackets { }.&nbsp; What is the difference and why? var rankColumnsObjects = [/*Shift 0*/{startgreen: 66, startyellow: 95, startred: 100}, /*Feeble*/{startgreen: 61, startyellow: 91, startred: 100}, /*Poor*/{startgreen: 56, startyellow: 86, startred: 100}, /*Typical*/{startgreen: 51, startyellow: 81, startred: 98}, /*Good*/{startgreen: 46, startyellow: 76, startred: 98}, /*Excellent*/{startgreen: 41, startyellow: 71, startred: 95}, /*Remarkable*/{startgreen: 36, startyellow: 66, startred: 95}, /*Incredible*/{startgreen: 31, startyellow: 61, startred: 91}, /*Amazing*/{startgreen: 26, startyellow: 56, startred: 91}, /*Monstrous*/{startgreen: 21, startyellow: 51, startred: 86}, /*Unearthly*/{startgreen: 16, startyellow: 46, startred: 86}, /*Shift X*/{startgreen: 11, startyellow: 41, startred: 81}, /*Shift Y*/{startgreen: 7, startyellow: 41, startred: 81}, /*Shift Z*/{startgreen: 4, startyellow: 36, startred: 76}, /*Class 1000*/{startgreen: 2, startyellow: 36, startred: 76}, /*Class 3000*/{startgreen: 2, startyellow: 31, startred: 71}, /*Class 5000*/{startgreen: 2, startyellow: 26, startred: 66}, /*Beyond*/{startgreen: 2, startyellow: 21, startred: 61}]; var rankColumns = ["shift-0", "feeble", "poor", "typical", "good", "excellent", "remarkable", "incredible", "amazing", "monstrous", "unearthly", "shift-x", "shift-y", "shift-z", "class1000", "class3000", "class5000", "beyond"] var attackTypeResults = [/*White*/{ba: "Miss", ea: "Miss", sh: "Miss", te: "Miss", tb: "Miss", en: "Miss", fo: "Miss", gp: "Miss", gb: "Miss", es: "Miss", ch: "Miss", do: "None", ev: "Autohit", bl: "-6 CS", ca: "Autohit", stun: "1-10", slam: "Gr. Slam", kill: "En. Loss"}, /*Green*/{ba: "Hit", ea: "Hit", sh: "Hit", te: "Hit", tb: "Hit", en: "Hit", fo: "Hit", gp: "Miss", gb: "Take", es: "Miss", ch: "Hit", do: "-2 CS", ev: "Evasion", bl: "-4 CS", ca: "Miss", stun: "1", slam: "1 area", kill: "E/S"}, /*Yellow*/{ba: "Slam", ea: "Stun", sh: "Bullseye", te: "Stun", tb: "Hit", en: "Bullseye", fo: "Bullseye", gp: "partial", gb: "Grab", es: "Escape", ch: "Slam", do: "-4 CS", ev: "+1 CS", bl: "-2 CS", ca: "Damage", stun: "No", slam: "Stagger", kill: "No"}, /*Red*/{ba: "Stun", ea: "Kill", sh: "Kill", te: "Kill", tb: "Stun", en: "Kill", fo: "Stun", gp: "Hold", gb: "Break", es: "Reverse", ch: "Stun", do: "-6 CS", ev: "+2 CS", bl: "+1 CS", ca: "Catch", stun: "No", slam: "No", kill: "No"}]; var attackTypes = {ba:"Blunt Attack",ea:"Edged Attack",sh:"Shooting",te:"Throwing Edged",tb:"Throwing Blunt",en:"Energy",fo:"Force",gp:"Grappling",gb:"Grabbing",es:"Escaping",ch:"Charging",do:"Dodging",ev:"Evading",bl:"Blocking",ca:"Catching",stun:"Stun?",slam:"Slam?",kill:"Kill?"};
1656286853

Edited 1656287186
GiGs
Pro
Sheet Author
API Scripter
They are different variable types - anything with {} is a javascript object, and anything inside []&nbsp; is an array. Byond that, it's a very difficult question to answer consicely. You might be better off googling arrays, and javascript objects. In the previous post your first two lines are: var rankColumns = ["shift-0", "feeble", "poor", "typical", "good", "excellent", "remarkable", "incredible", "amazing", "monstrous", "unearthly", "shift-x", "shift-y", "shift-z", "class1000", "class3000", "class5000", "beyond"] var attackTypes = {ba:"Blunt Attack",ea:"Edged Attack",sh:"Shooting",te:"Throwing Edged",tb:"Throwing Blunt",en:"Energy",fo:"Force",gp:"Grappling",gb:"Grabbing",es:"Escaping",ch:"Charging",do:"Dodging",ev:"Evading",bl:"Blocking",ca:"Catching",stun:"Stun?",slam:"Slam?",kill:"Kill?"}; The first is an array (collection) of strings. You can get data out of that array by usign the array name, and the index of the item starting at o. So rankColumns[0] is "shift-0", and rankColumns[3] is "typical". Strings are another data type, and are encloded by " ", so rankColumns[0] is the string, shift-0 . This is a great formats for lists of things, or with a bit more complexity, tables. The second is in object, and it's easier to understand when written like this: var attackTypes = { ba:"Blunt Attack", ea:"Edged Attack", sh:"Shooting", /* and so on */ }; You have a collection of keys before the :, and the values after the :. In this case, each value is a string. A string is a collection of letters or words, attackTypes.ba is "Blunt Attack", and attackTypes.sh is "Shooting". Objects are a great format for simulating simple databases. Where it gets complex, is that each item in an array or object can be another array or object. The attackTypeResults object starts like this: const attackTypeResults = { white: { ba: 'Miss', ea: 'Miss', sh: "Miss" te: "Miss" tb: "Miss" en: "Miss" fo: "Miss" gp: "Miss" gb: "Miss" es: "Miss" ch: "Miss" do: "None" ev: "Autohit" bl: "-6 CS" ca: "Autohit" stun: "1-10" slam: "Gr. Slam" kill: "En. Loss"} }, green: { ba: "Hit" ea: "Hit" sh: "Hit" te: "Hit" tb: "Hit" en: "Hit" fo: "Hit" gp: "Miss" gb: "Take" es: "Miss" ch: "Hit" do: "-2 CS" ev: "Evasion" bl: "-4 CS" ca: "Miss" stun: "1" slam: "1 area" kill: "E/S" } Here you have one object like this: const attackTypeResults = { white: /*an object*/ green: /*another object */ } and here, white and green each hold another object. White is { ba: 'Miss', ea: 'Miss', sh: "Miss" te: "Miss" tb: "Miss" en: "Miss" fo: "Miss" gp: "Miss" gb: "Miss" es: "Miss" ch: "Miss" do: "None" ev: "Autohit" bl: "-6 CS" ca: "Autohit" stun: "1-10" slam: "Gr. Slam" kill: "En. Loss" } ba, ea, sh, etc are the keys of that table. You get the values from nested objects the same way, just add the key on to the end like this: attackTypeResults.white.ba equals "Miss" and attackTypeResults.white.ev equals "Autohit". One of the most useful things about objects is grabbing the values dynamically. Say you don't know ahead of time what the attack value is (ba, ev, kill, etc). So you create a variable to hold that, and call it damage . Then you can do attackTypeResults.white[damage] (notice there's no period here - the [ ] brackets replace it). Likewise you might not know the colour ahead of time, so you set that with another variable named colour . Leading to something like attackTypeResults[colour][damage] With approaches like this, you can set up tables ahead of time, then in your code grab which item you need as and when you need it. Objects and Arrays each open up powerful possibilities for coding. But they are big topics, too big for a simple forum post :) The simple answer to your question: [ ] denotes an array, and you grab the values from an array using a numbered index, and {} denates an object, and you grab its values using a named key.
Thanks GiGi.&nbsp; That was very informative.&nbsp; I was able to track this fairly well.&nbsp; We can revisit as needed later in the code.&nbsp;&nbsp;
1656342995

Edited 1656418983
timmaugh
Pro
API Scripter
GiGs gave a good primer on the differences between arrays and objects. It can be confusing at first understanding the ways you reference items in either structure because there is some overlap in the syntax. Basics Basic Array Rules Array items are identified by index, and arrays are based at 0 (so for an array of one item, length = 1, you would retrieve that item by referring to index 0). You can use an integer value or a variable that represents an integer: let jibblets = ['0', 'one', 2, 'thrice']; console.log(jibblets[1]); // outputs string: one let jibbyIndex = 1; console.log(jibblets[jibbyIndex]); // outputs string: one Basic Object Rules Objects have properties that are identified by key. The rules around key names are a little more lax than variable naming, but it's a good idea to use strings: let wigglebang = { &nbsp; prop1: 'fizzy', &nbsp; prop2: 3 &nbsp; prop3: {} // another object stored in this property }; console.log(wigglebang.prop1); // outputs string: fizzy That references the prop1 property directly from the object (with the dot notation). You can reference it with bracket notation if you supply the key name (in the same data type as the key name is: ie, as a string for a string, number for number). So the following formations will also retrieve that same property value: console.log(wigglebang['prop1']); console.log(wigglebang['prop' + 1]); let propName = 'prop1'; console.log(wigglebang[propName]); Regarding the naming rules being more lax for property names (keys), you can start property name keys with a number (disallowed for variable names), and you can have special characters in the key (i.e., 'background-image') (also disallowed for variables). With these formations, you have to use the bracket notation to retrieve the property value... and this is where the data type of the name matters. For instance: let wigglebang = { &nbsp; 1: 'it worked', 'background-image': '<a href="https://imgur.com/2343js" rel="nofollow">https://imgur.com/2343js</a>' }; console.log(wigglebang[1]); // outputs: it worked console.log(wigglebang['background-image']); // outputs: <a href="https://imgur.com/2343js" rel="nofollow">https://imgur.com/2343js</a>' You have to use these property names in brackets, and it has to be the right type (number for 1, string for the background-image property). Finally, having a variable that shares a name with a property on the object demonstrates the need to keep straight the method whereby you reference what you are looking for. let underjib = 'something'; let wigglebang = { &nbsp; underjib: 3, &nbsp; something: 'this is the value for the something key' }; In this case,&nbsp; underjib &nbsp;exists both as a variable and as a key on the wigglebang object. Note the difference: console.log(underjib); // referencing the variable outputs: something console.log(wigglebang.underjib); // dot notation outputs value of the property: 3 console.log(wigglebang[underjib]); // bracket notation outputs the value of the property represented by the variable: this is the value for the something key Because the underjib variable stored the name of a property on the wigglebang object ('something'), what you asked for effectively resolved to wigglebang.something . Nested Returns (ie, what does it mean to have multiple bracket sets?) In a javascript line, you can imagine that when an object reference closes you have the retrieved 'thing' in that place. For an array or an object with a simple data type like a string or a number, you've effectively retrieved that data. If it's a number, you can perform number-based operations on it (like math). If it's a string, you can perform string-based operations (like concatenating, slice-ing, indexOf, etc.). If what you retrieve is instead an array or object, you can continue to reference items or keys with further bracket notation (for both) or dot-notation (for an object). Multi-dimensional Arrays Arrays can contain various data types (in fact, each item can be of a different type, but that's a different discussion). If an array contains another array, we describe that as a "multi-dimensional array." As GiGs pointed out for objects, it helps to visualize the multiD array if it's written across multiple lines: let multiArray = [ &nbsp; ['banana', 'yellow'], &nbsp; ['lemon', 'yellow'], &nbsp; ['orange', 'orange'] ]; Written like that, you can begin to see the multiD array as a table. Row 0 has information for a banana, row 1 for a lemon, etc. In this case, when we return multiArray[2], we have returned the array of data for an orange. If we specifically want the color, we know we have that stored as the second item in the nested array (0-based means we'll use 1 for the second item): console.log(multiArray[2][1]); // outputs: orange Further nested arrays (more dimensions) would require further bracket structures. Objects as Properties on Objects As GiGs mentioned, object properties can store any data type, including another object: let parentObject = { &nbsp; banana: { &nbsp; &nbsp; color: 'yellow', &nbsp; &nbsp; profile: 'sweet' &nbsp; }, &nbsp; lemon: { &nbsp; &nbsp; color: 'yellow', &nbsp; &nbsp; profile: 'sour' &nbsp; }, &nbsp; orange: { &nbsp; &nbsp; color: 'orange', &nbsp; &nbsp; profile: 'sweet' &nbsp; } } Again, we can view the first level properties as a row, with the properties of the nested object as columns (or datapoints). As opposed to using nested arrays, we simply have keys by which we can directly access those datapoints. In this case, when a call to a first level property resolves (ie, parentObject.banana ), we have retrieved another object. If we wanted to return the color property of the returned object, we can use any of the object notation methods described above: console.log(parentObject.banana.color); // outputs: yellow console.log(parentObject.banana['color']); // outputs: yellow console.log(parentObject['banana']['color']); // outputs: yellow console.log(parentObject['banana'].color); // outputs: yellow let propWeWant = 'color'; console.log(parentObject.banana[propWeWant]); // outputs: yellow Design: When to Use Arrays vs Objects If you've made it this far, you can probably see advantages and limitations of each type for representing data. If you have a fixed set of datapoints for a thing (a name, color, profile), or a fixed table of information, an object can be easier. Objects can also be easier the deeper the nesting goes... or if you don't know what order members will come (is the first item a banana, or a lemon?). On the other hand, arrays can be more flexible if you just need to build up a set of related items and you don't know how many items you're going to have before you're done (for instance, getting all tokens on a page, or adding the results of a die roll that could explode, etc.). POST EDIT: Object &amp; Array Interaction I forgot to include... a property on an Object can contain an array, and an array can contain an object. In each case, it's important to know what you get back when the reference closes. let parentObject = { &nbsp; associatedThings: ['timmaugh','GiGs', 'rcbricker'] }; console.log(parentObject.associatedThings[0]); // outputs: timmaugh let deepArray = [ &nbsp; { associatedThings: ['timmaugh','GiGs', 'rcbricker'] }, &nbsp; { associatedThings: ['keith', 'timmaugh'] } ]; console.log(deepArray[0].associatedThings[2]); // outputs: rcbricker
Thanks timmaugh,&nbsp; This was more of a brain twist, but I've used arrays and objects in VBA so tracked most of this.&nbsp; kinda.&nbsp; Lol.&nbsp;&nbsp; In this instance with the code starting with setting up variables contain an array and objects, what is the best way forward to do this.&nbsp; Should we stay with what Sarah did or is there a better way?&nbsp; I am thinking that for the most part this section is being built to handle the conflict resolution via the universal table.&nbsp; So this part should work just like the player would resolve the issue via a hard copy of the chart.&nbsp; roll dice, modify with karma, check results against appropriately ranked column (modified by any column shifts), check color result against combat action apply results to defender (if any).
1656366913
timmaugh
Pro
API Scripter
Here is the start of a structure. This returns the special case text at the top of the chart (ie, 'hit', 'slam', 'stun', etc.). More information could be tracked along the way (like what the start column was, what the offset column became, etc.), but like I said, this is the start of a structure. Change it as you need. Assumptions I made along the way: ...attacks will have only 1 type (only one column need be consulted from the set of 'edge attack', 'killing', etc. ...the attacking character will be selected ...the attributes representing Fighting, Agility, Strength, and Endurance are on the sheet and are accessible via lowercase (typically true)&nbsp; ...if your offset would move the column beyond the edge of the table, stop at the first/last column I have this working in a console version (you can drop it in a console outside of Roll20), but when you have to return something from the character sheet (dynamically getting the attribute for the column to start in), you have to take it to Roll20... so this is the version that drops that into the revealing module pattern I discussed earlier. The command line it expects is: !fase --type|&lt;attack type&gt; --offset|&lt;number&gt; The type is mandatory; offset is optional. Argument delimiter can be a pipe or a hash, so this is functionally equivalent: !fase --type#&lt;attack type&gt; --offset#&lt;number&gt; There isn't any "nice" reporting (like, "Hey, you didn't select a token", or "hey, that token doesn't represent a character"). It will just look like the script does nothing if the appropriate conditions aren't met. However, I marked the spots where you would want to send that kind of notification out. Also, it doesn't output anything except a statement to the script's log panel (in the script page for the campaign) reporting the output of all of the figuring. So if you want to see the results of your command line, check there, or change the script to output a chat message. While I tested the language pulling the attack result, the part of the script tying in Roll20 functionality is untested (I didn't have time to mock up an environment). // opening comment prevents poor closure management in previous script var API_Meta = API_Meta || {}; API_Meta.FASERIP = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; { &nbsp; &nbsp; try { throw new Error(''); } catch (e) { API_Meta.FASERIP.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (5)); } } const FASERIP = (() =&gt; { &nbsp; &nbsp; // ================================================== &nbsp; &nbsp; // VERSION &nbsp; &nbsp; // ================================================== &nbsp; &nbsp; const apiproject = 'FASERIP'; &nbsp; &nbsp; API_Meta[apiproject].version = '0.0.1'; &nbsp; &nbsp; const vd = new Date(1656364376097); &nbsp; &nbsp; const versionInfo = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; log(`${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} -- offset ${API_Meta[apiproject].offset}`); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; }; &nbsp; &nbsp; const getResultColor = ((c, r, o) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const columnBreakpoints = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-0': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '66', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '95', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '100' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; feeble: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '61', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '91', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '100' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; poor: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '56', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '86', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '100' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; typical: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '51', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '81', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '98' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; good: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '46', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '75', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '98' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; excellent: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '41', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '71', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '95' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; remarkable: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '36', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '66', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '95' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; incredible: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '31', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '61', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '91' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; amazing: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '26', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '56', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '91' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; monstrous: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '21', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '51', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '86' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; unearthly: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '16', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '46', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '86' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-x': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '11', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '41', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '81' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-y': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '07', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '41', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '81' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-z': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '04', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '36', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '75' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class1000: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '36', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '75' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class3000: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '31', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '71' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class5000: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '26', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '66' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; beyond: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '21', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '61' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; const readTable = (col = '', roll, offset = 0) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!columnBreakpoints[col.toLowerCase()]) return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let lcCol = col.toLowerCase(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let colSet = Object.keys(columnBreakpoints); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let breakpoints; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (offset &gt; 0) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; breakpoints = columnBreakpoints[colSet[Math.min(colSet.length - 1, colSet.indexOf(lcCol) + offset)]]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (offset &lt; 0) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; breakpoints = columnBreakpoints[colSet[Math.max(0, colSet.indexOf(lcCol) + offset)]]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; breakpoints = columnBreakpoints[lcCol]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (roll &lt;= breakpoints.startG) return 'white'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (roll &lt;= breakpoints.startY) return 'green'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (roll &lt;= breakpoints.startR) return 'yellow'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 'red'; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; return (c, r, o) =&gt; readTable(c, r, o); &nbsp; &nbsp; })(); &nbsp; &nbsp; const attackTypeResults = { &nbsp; &nbsp; &nbsp; &nbsp; white: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: 'Miss', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: 'Miss', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "None", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "Autohit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "-6 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Autohit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "1-10", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "Gr. Slam", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "En. Loss" &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; green: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Take", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "-2 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "Evasion", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "-4 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "1", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "1 area", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "E/S" &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; yellow: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "Slam", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Bullseye", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Bullseye", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Bullseye", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "partial", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Grab", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Escape", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Slam", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "-4 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "+1 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "-2 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Damage", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "No", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "Stagger", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "No" &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; red: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "Hold", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Break", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Reverse", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "-6 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "+2 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "+1 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Catch", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "No", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "No", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "No" &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const getStartingCol = (character, attType) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let attrName = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "fighting", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "fighting", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "endurance", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "fighting", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "endurance", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "endurance", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "endurance" &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; if (!character || !attrName[attType]) return; &nbsp; &nbsp; &nbsp; &nbsp; return getAttrByName(character.id, attrName[attType]); &nbsp; &nbsp; }; &nbsp; &nbsp; const handleInput = (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== 'api' || !/^!fase/.test(msg.content)) return; &nbsp; &nbsp; &nbsp; &nbsp; if (!msg.selected || !msg.selected.length) return; // can put notification that a selected token is required, here &nbsp; &nbsp; &nbsp; &nbsp; let argrx = /([^#|]+)(?:#|\|)?(.*)/g, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; attType = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; offset = 0; &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+--/)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // split at argument delimiter &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .slice(1)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // drop the api tag &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .forEach(a =&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // split each arg at # or |, (foo#bar becomes [foo, bar]) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; argrx.lastIndex = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (argrx.test(a)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; argrx.lastIndex = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res = argrx.exec(a); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (res[1].toLowerCase()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'type': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; attType = res[2]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'offset': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; offset = !isNaN(Number(res[2])) ? Number(res[2]) : 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; if (attType === '') return; // can put notification here that no attack type was chosen &nbsp; &nbsp; &nbsp; &nbsp; let character = findObjs({ type: 'character', id: findObjs({ type: 'token', id: msg.selected[0] })[0].get('represents') }); &nbsp; &nbsp; &nbsp; &nbsp; if (!character) return; // can put notification that no character was found for the selected token, here &nbsp; &nbsp; &nbsp; &nbsp; let roll = randomInteger(100); &nbsp; &nbsp; &nbsp; &nbsp; let startingCol = getStartingCol(character, attType); &nbsp; &nbsp; &nbsp; &nbsp; let colorResult = getResultColor(startingCol, roll, offset); &nbsp; &nbsp; &nbsp; &nbsp; if (!attackTypeResults[colorResult] || !Object.keys(attackTypeResults[colorResult]).includes(attType.toLowerCase())) return; &nbsp; &nbsp; &nbsp; &nbsp; log(attackTypeResults[colorResult.toLowerCase()][attType.toLowerCase()]); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; }; &nbsp; &nbsp; regHandlers = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; versionInfo(); &nbsp; &nbsp; &nbsp; &nbsp; regHandlers(); &nbsp; &nbsp; }); &nbsp; &nbsp; return { &nbsp; &nbsp; }; })(); { try { throw new Error(''); } catch (e) { API_Meta.FASERIP.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.FASERIP.offset); } }
well that is a lot to digest.&nbsp; Hell looks like it does at least 80% of the original code. Here are some questions...noted as comments in the code either on the same line or above/below it: // opening comment prevents poor closure management in previous script var API_Meta = API_Meta || {}; //what is the purpose of this line? API_Meta.FASERIP = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; //and this one. I cannot figure out what they are doing. { &nbsp; &nbsp; try { throw new Error(''); } catch (e) { API_Meta.FASERIP.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (5)); } }&nbsp;&nbsp;&nbsp;&nbsp;//above looks like an error catcher? const FASERIP = (() =&gt; { &nbsp; &nbsp; // ================================================== &nbsp; &nbsp; // VERSION &nbsp; &nbsp; // ================================================== &nbsp; &nbsp; const apiproject = 'FASERIP'; &nbsp; &nbsp; API_Meta[apiproject].version = '0.0.1'; &nbsp; &nbsp; const vd = new Date(1656364376097); &nbsp; &nbsp; const versionInfo = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; log(`${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} -- offset ${API_Meta[apiproject].offset}`); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; };&nbsp;&nbsp;&nbsp;&nbsp;//last 6 lines looks like it is setting and checking a version. Next line question: what does c, r and o represent? &nbsp; &nbsp; const getResultColor = ((c, r, o) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; const columnBreakpoints = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-0': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '66', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '95', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '100' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; feeble: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '61', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '91', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '100' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; poor: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '56', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '86', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '100' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; typical: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '51', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '81', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '98' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; good: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '46', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '75', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '98' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; excellent: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '41', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '71', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '95' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; remarkable: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '36', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '66', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '95' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; incredible: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '31', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '61', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '91' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; amazing: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '26', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '56', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '91' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; monstrous: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '21', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '51', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '86' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; unearthly: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '16', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '46', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '86' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-x': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '11', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '41', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '81' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-y': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '07', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '41', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '81' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'shift-z': { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '04', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '36', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '75' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class1000: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '36', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '75' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class3000: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '31', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '71' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class5000: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '26', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '66' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; beyond: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startG: '02', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startY: '21', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; startR: '61' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; };// what does let do? &nbsp; &nbsp; &nbsp; &nbsp; const readTable = (col = '', roll, offset = 0) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!columnBreakpoints[col.toLowerCase()]) return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let lcCol = col.toLowerCase(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let colSet = Object.keys(columnBreakpoints); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let breakpoints; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (offset &gt; 0) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; breakpoints = columnBreakpoints[colSet[Math.min(colSet.length - 1, colSet.indexOf(lcCol) + offset)]]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (offset &lt; 0) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; breakpoints = columnBreakpoints[colSet[Math.max(0, colSet.indexOf(lcCol) + offset)]]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; breakpoints = columnBreakpoints[lcCol]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (roll &lt;= breakpoints.startG) return 'white'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (roll &lt;= breakpoints.startY) return 'green'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (roll &lt;= breakpoints.startR) return 'yellow'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 'red'; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; return (c, r, o) =&gt; readTable(c, r, o); &nbsp; &nbsp; })(); &nbsp; &nbsp; const attackTypeResults = { &nbsp; &nbsp; &nbsp; &nbsp; white: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: 'Miss', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: 'Miss', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "None", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "Autohit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "-6 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Autohit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "1-10", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "Gr. Slam", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "En. Loss" &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; green: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Take", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "-2 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "Evasion", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "-4 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Miss", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "1", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "1 area", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "E/S" &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; yellow: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "Slam", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Bullseye", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Hit", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Bullseye", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Bullseye", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "partial", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Grab", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Escape", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Slam", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "-4 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "+1 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "-2 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Damage", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "No", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "Stagger", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "No" &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; &nbsp; &nbsp; red: { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "Kill", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "Hold", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "Break", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "Reverse", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "Stun", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "-6 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "+2 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "+1 CS", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "Catch", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "No", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "No", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "No" &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const getStartingCol = (character, attType) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let attrName = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ba: "fighting", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ea: "fighting", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sh: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; te: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tb: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fo: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gp: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gb: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; es: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch: "endurance", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; do: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ev: "fighting", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bl: "strength", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ca: "agility", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stun: "endurance", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; slam: "endurance", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kill: "endurance" &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; if (!character || !attrName[attType]) return; &nbsp; &nbsp; &nbsp; &nbsp; return getAttrByName(character.id, attrName[attType]); &nbsp; &nbsp; }; &nbsp; &nbsp; const handleInput = (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== 'api' || !/^!fase/.test(msg.content)) return; &nbsp; &nbsp; &nbsp; &nbsp; if (!msg.selected || !msg.selected.length) return; // can put notification that a selected token is required, here*** the below let needs some explanation &nbsp; &nbsp; &nbsp; &nbsp; let argrx = /([^#|]+)(?:#|\|)?(.*)/g, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; attType = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; offset = 0; &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+--/)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // split at argument delimiter &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .slice(1)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // drop the api tag &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .forEach(a =&gt; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // split each arg at # or |, (foo#bar becomes [foo, bar]) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; argrx.lastIndex = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (argrx.test(a)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; argrx.lastIndex = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; res = argrx.exec(a); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (res[1].toLowerCase()) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'type': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; attType = res[2]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case 'offset': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; offset = !isNaN(Number(res[2])) ? Number(res[2]) : 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; if (attType === '') return; // can put notification here that no attack type was chosen &nbsp; &nbsp; &nbsp; &nbsp; let character = findObjs({ type: 'character', id: findObjs({ type: 'token', id: msg.selected[0] })[0].get('represents') }); &nbsp; &nbsp; &nbsp; &nbsp; if (!character) return; // can put notification that no character was found for the selected token, here &nbsp; &nbsp; &nbsp; &nbsp; let roll = randomInteger(100); &nbsp; &nbsp; &nbsp; &nbsp; let startingCol = getStartingCol(character, attType); &nbsp; &nbsp; &nbsp; &nbsp; let colorResult = getResultColor(startingCol, roll, offset); &nbsp; &nbsp; &nbsp; &nbsp; if (!attackTypeResults[colorResult] || !Object.keys(attackTypeResults[colorResult]).includes(attType.toLowerCase())) return; &nbsp; &nbsp; &nbsp; &nbsp; log(attackTypeResults[colorResult.toLowerCase()][attType.toLowerCase()]); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; }; &nbsp; &nbsp; regHandlers = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; versionInfo(); &nbsp; &nbsp; &nbsp; &nbsp; regHandlers(); &nbsp; &nbsp; }); &nbsp; &nbsp; return { &nbsp; &nbsp; }; })(); { try { throw new Error(''); } catch (e) { API_Meta.FASERIP.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.FASERIP.offset); } }
1656378837
The Aaron
Roll20 Production Team
API Scripter
The API_Meta stuff is something I started adding to all my scripts to assist in debugging.&nbsp; I talk about it here:&nbsp; <a href="https://app.roll20.net/forum/permalink/9772430/" rel="nofollow">https://app.roll20.net/forum/permalink/9772430/</a> You might find this post useful also:&nbsp;<a href="https://app.roll20.net/forum/permalink/9790892/" rel="nofollow">https://app.roll20.net/forum/permalink/9790892/</a>
1656385667

Edited 1657119396
timmaugh
Pro
API Scripter
Let me see if I can help explain... First, this whole thing with the API_Meta object is a discovery from Aaron. I'm just explaining it. =D var API_Meta = API_Meta || {}; //what is the purpose of this line? That line gets to a later question of yours, too: // what does let do? There are various ways to declare variables: var, let, and const. Originally, you could only use var , so older code that hasn't been updated (or doesn't require updating) will probably feature this heavily. let is generally favored these days (with good reason... read this article to understand why). In general, let is more particular than var , which helps keep you from making mistakes (and const even more so). One difference between var and let that matters to this discussion, though, is hoisting. Think of hoisting like the compiler pulling all of your var -declared variables to the top of the scope where they are available. In effect, they are available before they are actually encountered in the code: console.log(theVar); var theVar = 'This works!'; EDIT : As GiGs notes, later in the thread, only the declaration is hoisted... not the initialized value. So the logging statement will output "undefined" since the variable is there and available (hoisted), it just hasn't been assigned a value. From a VBA perspective, you would have had to write that in an order more like: dim theVar as string theVar = "The works!" debug.print(theVar) So, even though var declarations can be problematic, there is an important use for them... when you don't know whether the variable has been declared before (by you or someone else). That "someone else" part matters since all of the scripts in your game are compiled into a single file before being added to the game. You'll notice that the line: var API_Meta = API_Meta || {}; ...is actually outside of the FASERIP function closure. It is at the effective "global" scope for all user scripts. If other scripts already have done this, we won't overwrite it (the logical OR operation will stop at the first truthy option... either API_Meta can be assigned to API_Meta, or (if API_Meta has not been declared yet), it will be assigned to be an empty object. Now that we know API_Meta is there -- and is available to all scripts and will survive all script redeclarations provided they all follow the above pattern -- we can use it. API_Meta.FASERIP = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; //and this one. I cannot figure out what they are doing. { &nbsp; &nbsp; try { throw new Error(''); } catch (e) { API_Meta.FASERIP.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (5)); } }&nbsp; &nbsp; //above looks like an error catcher? We're starting to stuff data into a property of the API_Meta object. The property is for THIS script (FASERIP), so that's what we name it. First we default a couple of values in, then we use a try/catch block to purposefully throw and catch an error. When we catch it, the parsing determines what line it happens on (the last value in the math, in this case a 5, is the line ON WHICH this error throwing happens). The result of the math is the offset of this script from the start of all code in the game. Remember, scripts are compiled into a single file, but we don't know where one begins and another ends. So when the sandbox breaks and reports an error at line 15603...? What script is that in? *blank looks all around* This is a way to figure that out. Look at line 16, in the versionInfo() function: &nbsp; &nbsp; &nbsp; &nbsp; log(`${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} -- offset ${API_Meta[apiproject].offset}`); That throws a statement at the script log when it runs. It runs when the 'ready' event is issued (from line 288): &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; versionInfo(); &nbsp; &nbsp; &nbsp; &nbsp; regHandlers(); &nbsp; &nbsp; }); The end of the log line (line 16) shows the script's offset (as read from the API_Meta object... using a variable with bracket notation -- recalling the previous post): API_Meta[apiproject].offset Since the versionInfo() function doesn't run until the 'ready' event is issued (after a sandbox reboot), by that time, one more line of code has run... line 297: { try { throw new Error(''); } catch (e) { API_Meta.FASERIP.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.FASERIP.offset); } } This line is, again, outside of the FASERIP function closure, and executes directly after the first try/catch block. By now, we have the API_Meta.FASERIP.offset value, so we can use that math to understand how many lines are in the FASERIP function and assign that to the lineCount property. Note in passing that the apiproject variable is block scoped, so it is available within the FASERIP function closure where it is declared. That's why, WITHIN the FASERIP function (and after the let declaration), we can do: API_Meta[apiproject] However, outside of that function, we have to explicitly reference the property by name: API_Meta.FASERIP How the API_Meta Object Helps Now, if you get an error that references line 15603, you can look at the last boot-up of your API sandbox and see that ScriptA reported an offset of 1200 (it also used the API_Meta object trick), ScriptB reported an offset of 5201, and FASERIP reported an offset of 15502. The difference between 15603 and 15502 is 101, so you would look at line 101 in the FASERIP script as a potential source for the error. I'll answer more of your questions in the next post...
1656385770
timmaugh
Pro
API Scripter
The Aaron said: The API_Meta stuff is something I started adding to all my scripts to assist in debugging.&nbsp; I talk about it here:&nbsp; <a href="https://app.roll20.net/forum/permalink/9772430/" rel="nofollow">https://app.roll20.net/forum/permalink/9772430/</a> You might find this post useful also:&nbsp; <a href="https://app.roll20.net/forum/permalink/9790892/" rel="nofollow">https://app.roll20.net/forum/permalink/9790892/</a> Heh... didn't realize that link/discussion was available... then I walked away from my post and finished a movie with my son. Returned, finished the post, and pushed "Submit" only to realize that the source of the trick, himself, had responded. =D
1656386798

Edited 1656388565
timmaugh
Pro
API Scripter
Looks like there's only one other question: // ... Next line question: what does c, r and o represent? const getResultColor = ((c, r, o) =&gt; { That getResultColor is defining a function. I actually built it as what is called an immediately invoked function expression (IIFE), which might be a little overkill, here. It works, I'm just not going to go into detail on an IIFE... because you probably could do it without it. The important thing, though, is that since it is a function, you can scan down for the executable code (what is actually run when the function is called). It's really just one line, at the end (line 133): &nbsp; &nbsp; &nbsp; &nbsp; return (c, r, o) =&gt; readTable(c, r, o); Those are being passed to the readTable() function. And what is the readTable() function expecting? Line 114: &nbsp; &nbsp; &nbsp; &nbsp; const readTable = (col = '', roll, offset = 0) =&gt; { col is the starting column roll is the value of the roll offset is how many columns to offset (positive or negative) All of those pieces of information are needed to read the chart you provided (which is why I rolled this up as a self-contained thing). Now that I'm looking at it, I could have simplified things by just building the IIFE like this: const getResultColor = (() =&gt; { // ... snipping to line 133 &nbsp; return readTable; })(); That would simply pass *any/all* arguments you pass to the getResultColor() function into the readTable() function... and the readTable() function is only concerned (as we just saw) with the first three variables it sees (col, roll, and offset).
OK i think I understand what the meta is doing.&nbsp; It sounds like it is being used to catch errors in an effort to determine where a given script functionality is located in the sandbox.&nbsp; This helps with locating which script is firing the error and where to find the line inside said script.&nbsp; Is that about right? As for the c, r, o; thanks I was wondering more what the variables stood for.&nbsp; I did not connect the variables to line 114.&nbsp; Might have been seeing cross eyed from all that code by then.&nbsp;&nbsp; I have been busy last few days, I hope to get back to this soonest. Thanks, Timmaugh for the explanations.&nbsp; Thanks Aaron for swinging by and for the links.