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 .
×
Advertisement Create a free account

[Script] InsertArg -- script preamp, siege engine, menu maker, command line constructor

1597987509

Edited 1607055198
timmaugh
Pro
API Scripter
Current Releases: Read what was changed from version to version in the change log at the bottom of this post... Script Version Date InsertArg Core Engine 1.5.1 December 3, 2020 IA Core Library 1.5.1 December 2, 2020 XRay 1.2 September 16, 2020 Note: As of September 9, 2020, all three scripts have been rolled into one. This should make staying up to date easier as the script moves closer to getting in the R20 repo. Video Intro I recorded this video (linked lower down in the thread) as an introduction to the InsertArg and Xray scripts. This is definitely a script that is easier to SEE work than it is to READ about. It might pay to watch the video and then read about the particulars (below). InsertArg InsertArg is a script that gives you a way to interact with information in the game and feed it to the chat and/or other scripts, or to build your own output on the fly. It can be a little meta, so let's start with an image. InsertArg is... a script preamp... a script siege engine... a socket set of interchangeable and extensible tools... On a more concrete level, it's a way to extend other scripts, giving them functionality you might wish they had, and letting you *almost* code in the chat window. It solves many of the headaches of ad hoc necessity... like knowing how many targets to target in a command line (do you write your macro for 4 targets, when you might only need to target 1 NPC during the game?), or having to maintain multiple copies of a macro (one for straight usage, one for nested), or formatting particular character components. And like I said, it is, itself, extensible (more on that in a bit). Oh, it's also a menu builder, but that's like... the third or fourth coolest thing it does. It will also xray characters: ...and let you walk the sheet... ...drilling down on abilities, attributes, or repeating sections... Files Though the script began as three distinct files, they have been consolidated into one to make it easier to stay up to date. You'll have to get it from my GitHub . The file you need is: insertarg.js => the main script engine,                         the core library of functions,                         and XRay, a helper script to walk over character sheets and help construct the right calls Also, ignore the readme in that folder for now... I just haven't had time to update it. Concept Here's how it works. InsertArg is an on-the-fly text-replacement and text-insertion script, giving you custom hooks and command-line functions for what to get, where to get it, how to format it, and where/how to output it. In other words, you can build and/or change custom command lines using built in functions in the InsertArg library, either building stand-alone commands or prefilling portions of calls to other commands. We'll break the command syntax down in a bit, but for now let's just start small: !ia --whisper# --show#Because it is close to us // whispers: Because it is close to us But if you had a character named "Leroy", and Leroy had an ability named "Super Spell" that had an action that read: !welikethemoon --why#show ...and you modified your call to InsertArg just slightly... !ia --whisper#Leroy|Super Spell --show#Because it is close to us ...then the line that would reach the chat would be: !welikethemoon --why#Because it is close to us Leroy's original ability is unchanged, but we fed ad hoc insertions into the command line before it reached the chat. In this case, in the ability we used the hook of "show" as the marker for where to drop our replacement text. We could have used anything, but stayed with "show" since that was in our original example line (we'll get to what text can be used for hooks and built-in special hooks -- like "show" -- soon). Finally, when you realize that the text we inserted ("Because it is close to us") could be derived at the time of running our call (via our internal function library), you begin to see how flexible and powerful InsertArg can be. Syntax and Usage A call to insertarg begins with !ia, followed by arguments denoted by the double-hyphen. mapArg The very first argument (called the mapArg), tells insertarg what it's about to do, and where to find the information it needs. The mapArg can be one of these basic sorts: chat        ==>    sends output directly to chat, if possible, or load/button if not whisper     ==>    sends output directly to chat as whisper if possible, or load/button if not button     ==>    creates a button to execute the modified command line load        ==>    writes the altered command line to the designated storage object (store argument; default: InsertArg ability) menu        ==>    sends to chat as whispered menu Others, like handout, log, help, and config, are allowed, but are generally used just for internal house-keeping (like producing a config or help handout). More on those later. The second half of the mapArg (following a hash) is the source to draw your initial command line from. For chat, whisper, button, and load, you can enter an ability or macro as follows: --chat#Cake            ==>    if speaking as a player, this finds an ability on that character's sheet called 'Cake' --chat#macro|orDeath   ==>    this finds a macro named 'orDeath' --chat#Izzard|Cake     ==>    this finds an ability named 'Cake' on the character 'Izzard' Generally in InsertArg, where a character is called for, you can use any of a character id, character name, or token id provided the token represents a character, and InsertArg will figure out who you're talking about. If you do not specify a source for one of the four mapArg values mentioned (chat, whisper, button, load), your command line is prefilled with "show"... giving you a hook you can later replace. Looking back to our first example, we replaced the hook "show" with "Because it is close to us". Hook Arguments Most of your arguments will take the form of --hook#value, where you want to replace or insert before/after the specified hook with the value you have retrieved. In this way, your hooks are fully customizable, and not limited to anything specified by the InsertArg script, itself. // Izzard has an ability named 'Cake' that reads: It's more of a maneuver, really. !ia --whisper#Izzard|Cake --more#less --really#if you know what I mean // produces: It's less of a maneuver, if you know what I mean. InsertArg defaults to replacing every instance of a hook with the specified value. Since hooks are processed left to right, they are chainable, and can work with what was left to them of the source command line after previous hook argument have processed. // Izzard has an ability named 'Cake' that reads: It's more of a maneuver, really. !ia --whisper#Izzard|Cake --more#less --less#part // produces: It's part of a maneuver, really. Hook arguments can be anything you wish except for the following list of special arguments: bg        ==>    base background color for buttons css       ==>    free css input for styling buttons store     ==>    where to drop the altered command line, if necessary (default: InsertArg) cmd       ==>    a special hook that represents the entirety of the command line, as it stands when processed In the case of bg, css, and store, these arguments will be processed before any hook argument, and will not be fed to the internal parser. The special argument cmd will be treated like any other hook, processed in its turn (left to right), except using the entirety of the command line at that point as the hook. Hook Consumption One last thing to understand before we get to the really good stuff (the internal library of functions available to you), and that's how to handle hooks so that you always know (or can imagine) the state of your command line at any given time. As I mentioned, InsertArg defaults to complete replacement of every instance of a hook found in the command line at the time of processing that hook. The entire hook is consumed and everything in your value is put in its place... and that happens everywhere we find that particular hook. There are special characters that can alter this behavior. --^^hook#value    ==>    inserts value before hook, leaves hook in the command line --hook^^#value    ==>    inserts value after hook, leaves hook in the command line By not consuming the hook, you gain a way to make additions to your command line, but you also leave the hook to be reusable for future hook arguments. You'll see this more when we build a menu. Also, these before/after constructions, when paired with the special hook 'cmd' (representing the entire command line) can be a way to prepend/append info to your command line. Greedy Hooks, and Lazy Hooks This one is a bit more esoteric, and it might make more sense after you read about the internal functions to understand the sort of things you can return. Again, the typical behavior is that every instance of a hook receives ALL of the value you have entered (or derived). What if you want to instead spread the return over several instances of the hook, giving each an element from the returned value? Or what if you want to deliver all of the payload to the first hook and only the first hook, leaving other instances of the hook behind? For this you will want a lazy or a greedy hook. --hook++#value        ==>    greedy hook; the first instance of the hook will get the value, leaving nothing for other instances of hook --++hook|d#value     ==>    lazy hook; the value will be split on the delimiter (d),                              with the first resulting element going to the first hook, the second to the second, etc Lazy hooks that are not consumed before the list of returned values is spent remain in the command line. The pipe character ("|") denotes  the division of hook and delimiter for a lazy hook, but you may still have pipes in your hook and/or your delimiter; your delimiter may only start with a pipe, however if it is ONLY the pipe character (short of escaping; see edit note below examples). Here are examples:                          HOOK        DELIM --++hook|d        ==>   hook         d --++hook||        ==>   hook         | --++hook||d       ==>   hook|        d --++hook||d|      ==>   hook|        d| EDIT: If you get the code after 8 o'clock August 21 2020, the delim for a lazy hook can be escaped with paired ticks(``); this syntax is consistent throughout the core library of functions elsewhere delimiters are used. Combining Insert and Greedy/Lazy Hooks The syntax for combining and greedy/lazy hooks is complementary, so you could have formations like this: GREEDY INSERT AFTER --hook^^++        ==OR== --hook++^^ GREEDY INSERT BEFORE --^^hook++ LAZY INSERT AFTER --++hook^^|d LAZY INSERT BEFORE --++^^hook|d     ==OR== --^^++hook|d The order of insertion position versus lazy/greedy does not matter. If you designate something as both lazy AND greedy, lazy wins. If you designate something as inserting before AND after, before wins. Internal Functions Text replacement games are fun, and all, but the real magic of the script is in the library of internal functions you can run. The core library has functions that let you access game components through the chat command line, retrieving data and formatting it as you necessary (and you can add to these functions easily, if you have a need to fill -- more on that later). The functions are fed through a recursive process  that detects functions in our library, processes them out to text, and passes them either to a wrapping function (if we are in a recursion) or to the hook (once all internal functions have been resolved). For instance, getsections is a function that will return the repeating sections for a character provided you supply a valid character identifier (name, id, or token_id): !ia --whisper --show#getsections{{!!c#Heretic !!d#`, `}} // returns: senses, moves, powers, skills ...but if you wanted to be able to call the same line from several places, or you wanted to give it to one of your players so they could use it without having to edit it, you could instead write the line with the getme function in place of your character's name: !ia --whisper --show#getsections{{!!c#getme{{}} !!d#`, `}} // returns: senses, moves, powers, skills Function Syntax Functions are recognized by their name and then double open-curly brackets that enclose any arguments to the function, followed by double closing curly brackets. Arguments are denoted using double exclamation marks, followed by the argument key, a hash, and then the argument value. Arguments that do not receive a value are assigned the boolean value 'true'. Certain arguments take special syntax for their values. This syntax can be found in the help for each function. Function Help The easiest way to access the help is to type !ia into the chat to get the IA Config screen. The screen will list all of the functions you have installed and available to you. Each will have a help button if the author has included such information (all of the functions in the core library come with help). You will also see on the IA Config screen the option to make a help handout, which is a document of the same information InsertArg will create in your game if you want it to. This can be a helpful resource to have open while you put together your InsertArg calls. Function Logging Also on the IA Config screen, you'll see the option to turn logging on or off. The recursive descent parsing engine built into IA is setup to output what every function is receiving and handing off along the way, writing this information to your log. This can be a helpful tool if your command line is not producing the result you think it should. By examining what each function handed off to the next, you can see where in the chain you might have an error. Function Example: Nesting Roll Templates Let's say that for my character, Heretic (Hero System 6E), I wanted to build a query of nicely formatted stat choices that trigger the roll template if I select that stat. The character sheet I'm using has various attributes for these stats, but the ones I'm interested in are named str_roll_formula, dex_roll_formula, con_roll_formula, etc. Each one has a roll template that looks like: &{template:hero6template} {{charname=@{character_name}}} {{action=Strength Roll}} {{roll=[[3d6]]}} {{target=12}} Those characters can break a query, and replacing them can be a pain. You obviously can't store the attribute values for these roll formulas with their characters replaced. But we can use InsertArg to give us the text we need: !ia --load --show#getattrs{{!!c#getme{{}} !!op#q !!f#x !!p#Select Stat !!frmt#fr#_roll_formula#``|uc !!efrmt#rslv#character_name#Heretic|n}} Here we are loading (into our default ability, InsertArg), the result of a getattrs{{}} function call. It is going to output (op) a query (q), using the prompt (p) 'Select Stat.' I derive my list of attributes to include by filtering (f) on executable (x). (These are my only executable stats for this character sheet, but if there had been others I could have further filtered the list.) Since I didn't want to see choices of 'str_roll_formula' and 'dex_roll_formula' in the query, I perform a format (frmt) to do a find/replace (fr), looking for '_roll_formula' and replacing it with nothing (``). In the same format argument, I tell it to be uppercase (uc). Finally, I format the executable side (efrmt) to resolve (rslv) any instances of @{character_name} with Heretic. (Resolve is a special find replace that wraps what you give it it @{} before searching. Last, in that same efrmt argument I tell it to nest (n) the executable string, performing the html entity replacement. The end result is... ?{Select Stat| STR,&{template:hero6template} {{charname=Heretic}} {{action=Strength Roll}} {{roll=[[3d6]]}} {{target=12}}| DEX,&{template:hero6template} {{charname=Heretic}} {{action=Dexterity Roll}} {{roll=[[3d6]]}} {{target=13}}| CON,&{template:hero6template} {{charname=Heretic}} {{action=Constitution Roll}} {{roll=[[3d6]]}} {{target=12}}| INT,&{template:hero6template} {{charname=Heretic}} {{action=Intelligence Roll}} {{roll=[[3d6]]}} {{target=13}}| PERCEPTION,&{template:hero6template} {{charname=Heretic}} {{action=Perception Roll}} {{roll=[[3d6]]}} {{target=13}} {{base=9}} {{stat=4}}| EGO,&{template:hero6template} {{charname=Heretic}} {{action=Ego Roll}} {{roll=[[3d6]]}} {{target=14}}| PRE,&{template:hero6template} {{charname=Heretic}} {{action=Presence Roll}} {{roll=[[3d6]]}} {{target=11}}} Menus (more Function Examples) Let's build a menu using the functions available to us and the mapArg menu. Menus can be added to the InsertArg engine (more on that in subsequent posts talking about how to code and expand InsertArg), but it ships with just one: the default menu. The default menu gives you an option for a title, a color, a fade value for an alternate color (not fully implemented yet) and a single hook named 'row.' It also comes with two row elements to choose from (row and elem), which you'll see in a moment. Menus can have any number of possible row types to choose from, so it's up to the menu author to communicate to you your options. When using a menu, your mapArg value is in the form of: --menu#name|color|fade If nothing is specified for these three options, the default menu (and its default options) will be loaded. Here are a couple of options for how to construct the menu. This first option (for the same character, Heretic), uses only row types of 'row' (no elem): !ia --menu --title#getme{{!!r#n}} --^^row#getrow{{!!t#STATS !!c#ffffff !!s#getattrs{{!!c#getme{{}} !!op#b !!f#f^#_roll_formula !!frmt#fr#_roll_formula#``|fr#ception#``|uc}}}} --^^row#getrow{{!!t#SKILLS !!f#.7 !!s#getrepeating{{!!c#getme{{}} !!s#skills !!sfxn#skill_name !!sfxa#skill_roll_formula !!op#b !!f#x}}}} --row#getrow{{!!t#POWERS !!c#ffffff !!s#getabils{{!!c#getme{{}} !!op#b !!f#^f#WayOf !!frmt#fr#WayOfThe#``|fr#WayOf#``}}}} I'll try to expand this write up later for what the script is doing (and why), but it's 1am, so I'll just give you the output: Note the middle row (SKILLS). I didn't like the way the buttons look rather hodge-podge in this section, so I decided to try with a row type of elem. Elem output is intended for exterior labels with consistent button text. Here is the call, slightly changed: !ia --menu --title#getme{{!!r#n}} --^^row#getrow{{!!t#STATS !!c#ffffff !!s#getattrs{{!!c#getme{{}} !!op#b !!f#f^#_roll_formula !!frmt#fr#_roll_formula#``|fr#ception#``|uc}}}} --^^row#getrow{{!!t#SKILLS !!f#.7 !!s#getrow{{!!r#elem !!t#SKILLS !!f#.7 !!s#getrepeating{{!!c#getme{{}} !!s#skills !!sfxn#skill_name !!sfxa#skill_roll_formula !!op#be !!rlbl#Roll !!f#x}}}}}} --row#getrow{{!!t#POWERS !!c#ffffff !!s#getabils{{!!c#getme{{}} !!op#b !!f#^f#WayOf !!frmt#fr#WayOfThe#``|fr#WayOf#``}}}} And, again, the output... More to come... Like I said, it's 1am, so while there is more that I wanted to highlight about what the script can do, that will have to wait. Unfortunately, we're also moving, so I can only say I will do my best over the next few days to update this thread with more information and to answer any questions anyone has -- or address any bugs. Speaking of which... there are going to be bugs. This is an expansive script that, because of the recursion and the functions interacting with each other, is a snake eating its own tail. There are interactions of the recursions that I just can't predict, and there are purposeful formations that you can force through that might break your sandbox. Don't do that. But if you do find a bug, please let me know and I will do my best to update the script to accommodate your use case. If you like the look of my buttons and don't want the Roll20 defaults, here is the config options I have setup: --bg#993333 --css#margin:2px 0px;height:10px;line-height:10px;background-image:radial-gradient(#f84545 , #aa0000 ); box-shadow:5px 5px 10px #888888; min-width:25px; border-radius: 6px; padding: 6px 8px; font-family:Arial, Calibri, Consolas, cursive;text-align:center; Run !ia, tell it to make your config file, then open the handout and paste those lines in over the two options that default to being there. Thanks A big thanks to Aaron, who first suggested the recursive descent parser as the way to solve the concept I was trying to build, and who has been more than generous with his time and knowledge as I inevitably ran into problems in a script this massive! EDIT : Adding XRAY examples XRAY (Helper Script) Xray is a helper script that can tell you a good bit of knowledge about a character sheet, whether for the purposes of building a call to InsertArg, examining a sheet to help you with another script, or just looking up information in game without having to flip over to your character sheet. Note that Xray requires InsertArg, but InsertArg runs just fine without Xray. However, for certain calls to InsertArg, a bit of knowledge about your character sheet is necessary. For instance, the way repeating sections are constructed (a group of related sub attributes sharing a common id in the middle of their names) requires you to know the sub attribute that provides the name for the element in the repeating section (i.e., the "Magic Missile" spell in your repeating section for spells), and which one provides the value or action/execution text. The good news is that compared to InsertArg, Xray couldn't be easier to run. Although there are a few constructions that can get you where you want to be immediately, 99% of the time that you would use Xray you'd just start from the top and enter into chat: !xray That's it. That will show you buttons for each of the characters you can control. Click on a button to continue drilling down on that particular character. Here I'll click on Heretic... ...to see a button for Abilities, Attributes, and every repeating section Heretic has an entry for (this samples sections for which the character has an entry, not every section available from the sheet, so certain characters may have more/less sections). Clicking on a button lets you continue to drill down on that section. For Abilities and Attributes, the default number of items to show in a single chat output is 25, and they will be alphabetized. You'll have Next and/or Previous buttons where appropriate to continue stepping through that section. For the repeating section, the output you will be presented with will be the view of all of the sub-attributes for an element in that section. You'll be asked for what entry to show (a number between 0 and 1 less than the number of elements in that section for that character). Don't worry if you enter one out of scope, you'll be informed what the highest value is. Your output will then look like this: ...you'll see a Next/Previous button as necessary, letting you walk through the entries in this repeating section. The first column is the suffix of the sub attribute (the full name will be more like repeating_powers_-Mssfn220_sjafdipsf0_power_name). You may also see a series of buttons for a given sub-attribute in this section, as you do for the sub attributes 'use_power_formula' and 'use_power2_formula'. These buttons indicate that the value of the sub attribute was detected to be executable. You can choose to View the text (maybe you want to know what it says to make sure it is the field you're looking for, or you want to know how to hook a replacement for an InsertArg call). You can choose to Exec(ute) the attribute and send it to chat, letting you confirm how it outputs. The last button, Build, will take a cross-sample of the repeating section with the associated sub-attribute as the action/executing script. Meaning, if I click on the Build button for use_power2_formula, I will get an output of buttons for all of the elements in this section (the "powers" section), and for each of them the value sent to chat will be their individual use_power2_formula. In this way, you can identify the naming sub attribute and the action sub-attribute for a call to getrepeating... as that requires sfxn (naming suffix) and sfxa (action suffix) arguments. For abilities and attributes (simple) output, the "Build" button is replaced with two buttons. "Starts" will give you a filtered list of the attributes/abilities that share the first 5 letters of the name of the associated attribute/ability. "Ends" will filter the list where the attribute/ability matches the last 5 characters. If you up-arrow in the chat, you can see that the call producing that output is a call to InsertArg, in this case: !ia --whisper --show#msgbox{{!!c#The following abilities were found matching the first 5 characters of the chosen ability. This list can be further refined. !!t#ELEMENTS !!btn#getabils{{!!c#-M4jpPEy0ScLWE54K2gM !!op#b !!f#^f#`WayOf`}} !!send#true !!wto#Heretic}} ...which is a whole lot because the juicy bit is nested in a msgbox function, but if you look in there, the button (btn) argument of the msgbox is: getabils{{!!c#-M4jpPEy0ScLWE54K2gM !!op#b !!f#^f#`WayOf`}} ...which is, itself, a discrete function call you can use elsewhere. A common workflow I use is to construct the call to a function and drop it in a chat to myself until I have it right, then insert it where it goes in a larger call. Here, I'll drop the getabils call into a simple line just to see the output: !ia --whisper --show#getabils{{!!c#-M4jpPEy0ScLWE54K2gM !!op#b !!f#^f#`WayOf`}} That would give me just the buttons, without the bounding message box. You can reuse that elsewhere (say, in menu calls). In fact, if I get the chance, I'll expand this message again, later, talking through the workflow of how to build up a call and tweak it into just what you need. Changelog InsertArg v1.5.1 - fixed problem of breaking the sandbox using "load" to drop the command line into an ability that did not yet exist v1.5 - allowed for full width sub-attributes in card output, corrected creating global handout process v1.4 - added card output to getrepeating, as well as a list (l) argument v1.31 - added the ability to use tick marks to denote an empty row title for menu output v1.3 - fixed problem from 1.2 consolidation, added buttons to read-to-chat, protected internal calls to IA within the parser v1.2 - consolidated filter, format and output options to main script to make them available to third party scripts v1.11  - fixed typos v1.1 - minor bug fixes and code enhancements invisible to user IA Core Library v1.5.1 - fixed output (op) as buttons in element rows of table (be); previously these had defaulted to attributes even if you sent abilities v1.5  - added 'emptyok' argument to getrepeating, getattrs, and getabils (suppresses 'no object found' message) v1.4 - added list (l) argument to getrepeating, as well as card output options v1.3 - bug fix, implemented protected calls to IA for reading buttons (allowing nested functions bearing future api calls) v1.2  - moved filter, format, and output options to main script, exposed version number for reporting on the IA config menu v1.1 - added 'lve' option to output argument, giving label-value output to elem rows in a menu; added 'ef' argument to getattrs, getabils, and getrepeating to allow filtering on the action/exec text (follows the same patterns as the filter argument (f)); added 'sfxrlbl' to getrepeating as an option to provide a unique roll label for buttons produced for elem output to a menu Xray v1.2 - changed the ordering of a repeating section to match the order on the sheet  (previously it had shown elements in creation order); also added fuller name information in header (repeating_section_rowID), as well as a position (your place in the set of rows in that repeating section); also added a button to get info about a given sub-attribute, including the full name with rowID, the full name using $0 (sheet position) nomenclature, and the individual ID of the sheet object. v1.11 - fixed bug where xraysheet wasn't getting the config object v1.1 - fixed- character name now at the top of all xray walks
1597992350

Edited 1597993801
dude.  this is really cool!!  can't wait to play with it.  you've basically built a CLI for roll20 chat.  talking about RDP and BNF brings me back to taking Programming Lang. in undergrad, yikes! not fun. a few questions. can you chain insertion hook with greedy/lazy form?  (something like ^^hook++#value or hook^^++#value?  or  ^^++hook|,#value1,value2 or ++hook^^|,#value1,value2...  etc.). i assume so but it wasn't clear from write up and is there a way to escape the delimiter? what is meant by this? your delimiter may only start with a pipe, however if it is ONLY the pipe character. anyway, kudos.  my only slight critique, if you permit my slight insolence, is that the command line input is a bit clunky and perhaps could be improved design-wise, but that's a small point compared to what's accomplished (also, maybe it's unavoidable? i don't know), which is a lot. EDIT: also, can you expand on how filtering works on getattr (other than getting all executables)?  how could i filter for some subset of executables with certain naming pattern say? 
1597993713

Edited 1597994202
GiGs
Pro
Sheet Author
API Scripter
aisforanagrams said: anyway, kudos.  my only slight critique, if you permit my slight insolence, is that the command line input is a bit clunky and perhaps could be improved design-wise That's my concern. It looks very powerful, and a massive achievement, but the syntax is very unfriendly. That's unfortunately going to limit this to people who are already comfortable with command line interfaces (programmers, basically) which is going to be a small number of potential users. Even something as simple as using readable words ( format instead of frmt , for instance) would make it more accessible. You might need an GUI for this CLI :)
GiGs said: That's my concern. It looks very powerful, and a massive achievement, but the syntax is very unfriendly. That's unfortunately going to limit this to people who are already comfortable with command line interfaces (programmers, basically) which is going to be a small number of potential users. You might need an GUI for this CLI :) I think as is, best way to use this would be to have a layer on top of this for specific use with a more simplified syntax. So a script that uses IA to do more specialized things with simpler, user-friendly syntax.  Similar to relationship between LaTeX and TeX (if you're familiar with the typesetting mark-up lang).
1598012224

Edited 1598021476
timmaugh
Pro
API Scripter
Thanks for the feedback, guys! It means a lot! To answer some of your points, anagram... 1. I updated the original post to note that insertion and greedy/lazy can work together, and how; good catch (coding this thing was difficult, but so was writing the help in such a way to catch everything but not be too dense!) =D 2. I updated the repo with a version that lets you escape the delimiter with enclosing ticks; this mirrors functionality in the core library anywhere a delimiter is used; again, good catch! 3. I added the explanation to the pipe presence in the delimiter to the original post, but the use of enclosing ticks obviates the pipe problem aisforanagrams said: EDIT: also, can you expand on how filtering works on getattr (other than getting all executables)?  how could i filter for some subset of executables with certain naming pattern say?  Sure. Filter and Format are chainable conditions, so each they'll take a pipe-separated list, and they will execute the filter/format from left to right. So if you wanted to get the executable elements with names that only begin with 'stat', you would do something like: !!f#x|^f#stat or if you wanted anything that contained 'formula' EXCEPT for the items that started with 'stat', you would do: !!f#x|^f^#formula|-^f#stat You should know, too, that the getattrs, getabils, getrepeating... maybe others... also takes a list argument (l), which can be a way for you to provide a specific list of things (pipe separated) to include instead of starting with all things. If a space is detected in the list argument, InsertArg assumes that is what you want for your label for that element anywhere it might show up (like on a button, for instance). Filters are still applied to your list if you specify them (so you could filter out something you explicitly included... it was on my radar for a release 2.0 to offer an argument of an exempt list... something like "include these no matter what the filter tells you". Also, since the list argument expects a pipe-separated list, you could feed the result of a getattrs call to the list argument of another getattrs call... like I said, snake eating its own tail. As for the complexity of the syntax, I hear you, I just don't see a way around it. I had to bypass the chat parsing and html encoding. I wondered about longer argument names, GiGs, but the command structure was getting so long, I thought I would stick with brevity. I suppose in a future release, if it's called for, I could include a disambiguation for arguments... keeping backward compatibility for things like 'c' but allowing the same arg to be 'character'. And I'm more than open to someone coding a helper/shell script for this. That would be awesome!
1598016347

Edited 1598016362
caveat, i have not played around with scripting APIs at all so i don't know for sure what i'm suggesting is possible with the API script can receive from the chat, etc.  so take it with grain of salt, but i think something like what i'm suggesting could be implemented. one syntactical improvement in future versions i would suggest is to separate function from arguments for that function.  so instead of something like "-- ^^hook++#value  " which is somewhat aesthetically displeasing, i'd get rid of the "--" part of the function ("--" should reserved for args), we could have for example, <function-name> --<arg1> --<arg2> etc.  much like how git syntax (and other command line tools) work. so instead of " -- ^^hook++#vals". we could possibly have something like "hook --before --greedy --values=vals". this could also have shorthands maybe something like "h -bg -v=vals"
3. I added the explanation to the pipe presence in the delimiter to the original post, but the use of enclosing ticks obviates the pipe problem so looking at the example, i gather what you mean is that delimiter cannot start with a pipe except when the delimiter IS the pipe.
1598020213
timmaugh
Pro
API Scripter
aisforanagrams said: 3. I added the explanation to the pipe presence in the delimiter to the original post, but the use of enclosing ticks obviates the pipe problem so looking at the example, i gather what you mean is that delimiter cannot start with a pipe except when the delimiter IS the pipe. Exactly. So, if you want your delimiter to begin with a pipe and contain more than just the pipe (like you wanted your delimiter to be: |--x--|  ), you would want to enclose that in tick marks. As for the syntax around 'hook'... hooks *are* arguments to the whole ia script call, which is why they are prepended with the double hyphen. Each hook is an argument whose value is fed into the recursion and replacement engines... and the internal functions can only be accessed on the value side of a hook#value arrangement. Now, as for how to structure the decorations around the hook... I think you may be on to something. Right now the arguments are detected with the 'any amount of white space followed by double hyphens' sort of regex, and then each hook is divided on the first hash. Within the hook, though, i could give the opportunity for nicer formatting of the syntax (some other way to parse out the greedy/lazy/insertion). I will think about how to do that for a subsequent release... maybe after finding more ways/suggestions to simplify the syntax.
1598022419
David M.
Pro
API Scripter
1598022662
timmaugh
Pro
API Scripter
Oh... wow... I just realized that the nature of this script lets you migrate your existing InsertArg calls if the invocation syntax changes... if i include an e-filter argument (filtering the executable or 'value' text of the thing you're looking at rather than the label or 'name'), you could filter your returned list of sheet objects (abilities or macros) to be just !ia invocations, then read a schema internally... migrating from release 1.0 to (current) might involve doing a replace on the variations of greedy/lazy hooks to the new syntax, for instance. It's a tad complicated on the back end, but nothing about this script was simple on the back-end!
1598023318

Edited 1598023418
timmaugh said: As for the syntax around 'hook'... hooks *are* arguments to the whole ia script call, which is why they are prepended with the double hyphen. Each hook is an argument whose value is fed into the recursion and replacement engines... and the internal functions can only be accessed on the value side of a hook#value arrangement. Now, as for how to structure the decorations around the hook... I think you may be on to something. Right now the arguments are detected with the 'any amount of white space followed by double hyphens' sort of regex, and then each hook is divided on the first hash. Within the hook, though, i could give the opportunity for nicer formatting of the syntax (some other way to parse out the greedy/lazy/insertion). I will think about how to do that for a subsequent release... maybe after finding more ways/suggestions to simplify the syntax. without knowing about how you've coded the parsing, i'll defer to you.  however, in future version you could implement the changes to handle something like that.  functionally, hooks are a kind of a procedure you run, which to me should be differentiated from pure data arguments in syntactical sense (though on the backend everything can be treated as data).  anyway, that's just my suggestion. and maybe this could be my first API project - if i'm motivated enough and find time to do it -  to put a more user friendly frontend to your great script ( a transpile script that essentially translate one syntactical form to yours and call your script).
1598023798
timmaugh
Pro
API Scripter
hahahaha...  lol @ David M. I like that!
1598035677
timmaugh
Pro
API Scripter
Edited the original post to include Xray functionality and usage, and how it can help with building InsertArg calls.
1598243794
timmaugh
Pro
API Scripter
Finally have a few minutes during our move, so let me return to a couple of things... First, let me say I'm absolutely open to simplifying/standardizing the call syntax to make it more accessible to more people. I wasn't (and I'm not) trying to be a pain, anagrams, I just think we were on different pages about what is an argument versus a function. I only think of the internal function library as "functions"... and those are only engaged on the right side of a key#value input. So, for an InsertArg call, it could look like this: !ia --whisper --this#thisvalue --store#myability --that#thatvalue That gets parsed on /s+--/, and the first argument gets handed off to mapArg. The rest get split on the first '#', so that they are [key, value] entries. The keys get tested to see if they are a part of the config object; keys that are properties to the config object get processed first. In the above example, 'store' is a property to the config object, so it gets processed before the others. The rest of the arguments have their value portions handed off to the RDP engine to see if we detect a function. Whatever we find, the text returned gets substituted into the key (the hook) as directed by the presence/lack of the greedy/lazy/before/after syntax (in the replacement engine)... so I guess on one hand I can see how you could think of the keys (the hooks) as functions, themselves, since we are performing operations on them, but it's hard to wrap my head around *not* prepending them with the double-hyphens as that is the way we detect their presence in the first place. That said, I can see the way something like this could work: --this !!before !!greedy #value But I also see some value in two up-carrots (I would use left/right carrots, except that they'll get eaten by html formatting) establishing which part of the hook gets replaced. In other words, everything between the ups would be replaced: Hook Syntax      ==>    Produces --hook#value     ==>    value    (default for no ups is: enclosed hook, ie, ^hook^ --^hook^#value   ==>    value    (same as above) --^ho^ok#value   ==>    valueok --ho^^ok#value   ==>    hovalueok --hoo^k^#value   ==>    hoovalue But that's still not as pretty... so if you have something better, I'm all ears.
1598245982
GiGs
Pro
Sheet Author
API Scripter
I would suggest having a function that interprets input and converts into the syntax you have created here. That allows you to create a prettier and more readable format for user input. I wont have time to study it for a while, or I'd have suggestions for how to go about that. 
1598265913

Edited 1598265987
GiGs said: I would suggest having a function that interprets input and converts into the syntax you have created here. That allows you to create a prettier and more readable format for user input. I wont have time to study it for a while, or I'd have suggestions for how to go about that.  yep.  that's exactly what a transpiler would do.  i'm tempted to try my hand on writing one.
1598269495
timmaugh
Pro
API Scripter
Now we're really getting...
tim.  i think i misunderstood your initial post.  i thought "hook" was a key word.  now i see it's some arbitrary string.  i think there's still a more readable design to this than it is current.  i'll mull on it during lunch break. cheers!
1598287664

Edited 1598296000
timmaugh
Pro
API Scripter
Extending InsertArg with Your Own Functions/Help I mentioned in the first post in this thread that IA was extensible, so I wanted to take a minute to talk about how to program for and expand the functionality of IA. That's really where the script will come to life, I think, because while I have the delivery vehicle in place (the replacement/recursion engines), I just don't have enough of a fluency or experience in the various corners of Roll20 to know what the needs might be. But there's a way that other scripters can help. To be clear: this is NOT necessary to use InsertArg as it stands; this is specifically for scripters who want to add to and expand what InsertArg can do. Input to Your Function A function that you would write for the IA library will be handed an object that will contain every argument the user supplied to your function (that is, every piece of information they included following a double exclamation mark). It will also contain 3 standard objects. m         ==>    the message object, content unaltered cfgObj    ==>    the IA config object for the user, containing the properties...         bg    ==>    background for buttons         css    ==>    free css for button styling         store  ==>    default ability location for command lines that cannot be sent to chat      (like those that require user-initiation) or that the user prefers to have simply get 'loaded'; (default: 'InsertArg'); created if it doesn't exist         label  ==>    text for a button if no other label is provided (default: 'Loaded Ability') theSpeaker    ==>    the 'who' of the message object with a few extra properties made available to you         speakerType    ==>    'character' or 'player'         localName    ==>    a speakerType-agnostic way to get the name of the speaker (character name or player name)         chatSpeaker    ==>    a formation of 'speakerType|chatSpeaker' for sending chat messages (see the msgbox function for more)         id    ==>    a speakerType agnostic way to get the id of the speaker (character or player); if you are specifically looking for the playerid, it is quicker to just get that off the message (m) object. So if your function was called "giveThemBack" and the user entered it into an IA call like this: !ia --whisper --show#giveThemBack{{!!when#After World War 2 !!who#Britain !!what#India and a number of other small countries}} ...your giveThemBack function would recieve an object like this: {     when: 'After World War 2',     who: 'Britain',     what: 'India and a number of other small countries',     m: { // original message object },     theSpeaker: { // speaker object described above},     cfgObj: { // config Object described above} } In this way, you can ask for (and expect) any sort of argument from your user and it will be handed to your function if they provide it. Just don't ask for an argument named 'm', 'theSpeaker', or 'cfgObj'... because whatever they input will get stepped on by the objects handed off by the script. =D (Note: if you don't know who Eddie Izzard is, I apologize that you won't get these references. You see, I apologize because people will then say, "There's a person of strong personality... I like to have--" Maybe we'd just better move on. Yes, I think we'd better had.) Handling the Input: A Destructuring Suggestion Referencing the properties of an object that is itself a property to another object that is a property to your argument object can be tedious (and take a lot of typing to drill down on the info you need). I prefer to use destructuring to render the properties out to discrete objects I can handle directly: const giveThemBack = ({     when: when = 'now',     who: who = '',     what: what,     m: m,     theSpeaker: theSpeaker,     cfgObj, cfgObj }) => { // function code goes here }; In destructuring an object, the left side of the colon is how the property exists in the object you are handed (the arguments). Make sure you match the name. The right side of the colon is how you will refer to it in your code (letting you relabel it). The right side also allows you to provide a default value for the argument you are destructuring into its own entity (like where I defaulted the 'when' argument to 'now'). Returning a Value from Your Function Your function should return an object with at least two properties: ret    ==>    text; this is what will be substituted into the hook and/or handed off to a bounding function              if your function is included in a nested call safe   ==>    boolean; whether it is safe to send what your function has produced to the chat; if any function returns false, the entire payload of the IA call is deemed unsafe to send to chat, and it is instead loaded, only The fact that the return is structured as an object allows you to return more data beyond just these two properties, if your function could be of use to other functions that would call it internally. More on that in a minute. Registering Your Function to InsertArg Making your function available to InsertArg is pretty straightforward... use the RegisterRule function IA exposes for you, and chain all of your functions as arguments. For instance, the core library does this in the on('ready') event: if (ia) ia.RegisterRule(puttext, getattr, getabil, getmacro, puttargets, getselected, getme, getsections, getrepeating, getattrs, getabils, gettokens); That will make your function available to the user, and it will make it appear in the !ia config message (as an "Available Function"). It will also make it available to other scripts to call and use your function. Next section talks about that... Calling an Internal Function All of the functions loaded and registered to InsertArg are available to other scripts via the "RunInternal" function IA exposes for you. Since this exposure is a public interface, you don't even have to be an InsertArg helper function to use them, but that's the topic for another day. The point is, if you need to run an internal function from your code, you can use the RunInternal() function and feed the appropriate parameters. Since you are calling it manually, be sure to pass all of the arguments the function might need, including the message object (m), theSpeaker, and cfgObj. And expect the function to return an object, as described above. As an example, here is how to use the nest() function (to handle html entity replacement except for attribute/ability calls). let querytext = '?{Select Stat to Apply|Primary Stat, @{Bob|FingerGuns}|Secondary Stat, @{Bob|PewPew}}'; querytext = ia.RunInternal("nest")({ s: querytext }); log(querytext.ret); // outputs: ?{Select Stat to Apply|Primary Stat, @{Bob|FingerGuns}|Secondary Stat, @{Bob|PewPew}&#125 Understanding the InsertArg Public Interface Here are the functions InsertArg exposes for you to use directly (not via the RunInternal function to get at the library, but by directly referencing off of the ia namespace object. To be clear, these are NOT available to the user, and they are NOT available through the RunInternal() function.         RegisterRule: registerRule,         RegisterMenu: registerMenu,         RegisterHelp: registerHelp,         RunInternal: runInternal,         BtnElem: btnElem,         BtnAPI: btnAPI,         MsgBox: msgbox,         CharFromAmbig: charFromAmbig,         AttrFromAmbig: attrFromAmbig,         AbilFromAmbig: abilFromAmbig,         PlayerFromAmbig: playerFromAmbig,         MacroFromAmbig: macroFromAmbig,         TokenFromAmbig: tokenFromAmbig,         ElemSplitter: elemSplitter,         GetIndivConfig: getIndivConfig,         HTMLCoding: htmlCoding,         Base64: base64,         CMDLineParser: cmdLineParser,         GetHelpArg: getHelpArg Registration RegisterRule we just talked about as the way to register your function. We also talked about RunInternal as the way to access these library functions. RegisterMenu and RegisterHelp I will get to in just a minute. FromAmbig The 'FromAmbig' set are ways to disambiguate user input and return the Roll20 object that is indicated (not text or the return object of a library function, but the actual Roll20 object). For instance, CharacterFromAmbig will first test if the input provided matches the id of a character; if not, it will test if it matches the name of a character; if that doesn't match it will check if the id matches a token's id, and if that token represents a character. If it finds a character, it returns the character object. Two special cases to be aware of in the ambig set are abilFromAmbig (for abilities) and attrFromAmbig (for attributes). These will check if the input provided matches the id of the appropriate type of object. If that fails, the next test they will look to check is if the data is structured as character|id or character|name. In other words, if the character Bob has an attribute called "FingerGuns" that has a Roll20 id of "-M123", the following information provided to attrFromAmbig will all succeed in returning the Roll20 object: -M123 Bob|-M123 Bob|FingerGuns Buttons Other functions that will be of great help to you are the BtnElem and BtnAPI. These will let you create buttons that utilize the speaker's config object, presenting a consistent look for all IA -generated buttons. Both functions take an object as an argument, and they both will look for properties on that object of: bg, css, and label. Passing the bg and css from the cfgObj will keep the look of the buttons consistent. BtnAPI expects an 'api' property that it will use as the target of the button it is building. BtnElem produces a button pulled from a character sheet, so it will need a 'charname' property, a 'store' property (the sheet element to create the button for), and an 'entity' property, which is the html entity for the @ of an attribute call (@) or the % of an ability call (%). MsgBox If you poke around with InsertArg long enough as a user, you'll bump into a function for which you didn't provide the right information, or where nothing was returned. These messages to the user are built using the MsgBox function. As with the buttons, using this function creates a consistent messaging look for the presentation of InsertArg information. The MsgBox function expects an object as an argument, and the object should have these properties: c         ==>    the message you would send to chat t         ==>    title for the box btn    ==>    a secondary text area that is a good place to drop buttons, to keep them separate from the message text send    ==>    whether to send this message immediately, or simply return it as the return object of a library function (most messages alerting the user to no return or an input error should be sent immediately) sendas    ==>    who to send the message as (default: 'API') wto    ==>    'whisper to'; if filled in, the message will be whispered to the designated target                  (typically a good idea to whisper to theSpeaker.localName) Others Most of the other functions/objects are perhaps less commonly needed, so you may not end up using them at all. ElemSplitter    ==>    when constructing output for menu elements with iterated 'elem' output, this provides                         delimiters for the list. Put ElemSplitter.inner between external label and the button                         of an elem row (for instance), and put ElemSplitter.outer between each row. GetHelpArg    ==>    similar to ElemSplitter, this provides a consistent bit of text to mark when a help entry                         should link to another help entry (more on this in the Help section) Base64    ==>    provided to have a standard Base64 encoding/decoding standard for any internal functions                         to drop/read information so they're all working from the same playbook; so far this hasn't                         proven necessary, but it's here if/when it is needed GetIndivConfig   ==>    gets the config object for the supplied speaker; probably not necessary to your function                         as you will be receiving the cfgObj for the user directly CMDLineParser    ==>    the parsing engine is exposed in case you would want to engage it for some purpose;                         this topic is too large to cover in an already too long post Building and Registering Help If the user runs !ia, they will receive a message listing the various functions that they can run, each with a button to provide them more information. They can also choose to build a Help Handout, so that they can keep it open while they work on perfecting the call to InsertArg. Both of these things are driven by the registration of help verbiage for each function. Help is built as an object. You can go to the bottom of the Core Library to see what building the object might look like. In short, your help should be built where each function is a property of the main object, and each function should have sub properties for msg and args. Here is an example for two functions named 'getFunky' and 'getPunky'. const help = {     getFunky: {         msg: 'Basic explanation of the getFunky function goes here. Make it clear to the user what the function does and how to use it.',         args: [             ['arg', 'structure arguments like this'],             ['arg2', 'args is an array of arrays'],             ['arg3', 'the first element should be the argument the user uses; second element is the explanatory text']         ]     },     getPunky: {         msg: 'Explanatory text for this function.',         args: [             ['a', 'explanatory text for argument a'],             ['b', 'explanatory text for argument b']         ]     } }; When you have all of the help verbiage in the document, you can register it to InsertArg using the RegisterHelp function: if (ia) ia.RegisterHelp(help); Special Help Sections Certain portions of help text might be long enough to warrant seeing it on its own, instead of having a verrry long message. Examples of this are the filter, output, and format arguments to various functions. Rather than making the function help entry extra long, these arguments are broken into their own help entries, and the user is given a button to link to them from the chat help for the function utilizing them. For instance, the getabils help entry shows buttons for three arguments: And clicking on the button for frmt brings up the format help entry: This is accomplished by means of creating the help entry using a special structure to the entry. Enter your primary function's help information (eg, getabils), and create another entry for the argument that has enough options or text to warrant its own help entry (eg, frmt). In the primary function's help entry, the text for the argument should be structured like this: ['frmt', `gethelp(format${ia.GetHelpArg()})`] Breaking that down, this is an argument entry, so you see the argument on the left, and the help text on the right. However, the right side is structured with the help entry named using the GetHelpArg() text (a standard suffix for these argument help entries, which is currently set to 'ArgOption', but which is defined in InsertArg, so utilizing the GetHelpArg() function will ensure that this continues to work should the definition ever change). That text, including the GetHelpArg() suffix, is wrapped in the text 'gethelp(' and ')' to trigger the creation of a button in the chat version of the help, or to create a "see [this section]" for the handout version of help. Obviously, the name of the help entry for the argument should match the text wrapped in the gethelp() syntax. In other words, when you tell it to "gethelp", make sure you are telling it the right name for the help entry to retrieve. That's It That's not too much, right? I know... there's a lot of information, and it can seem dense, but if you look at the Core Library you'll see this structure at work as an example. But once you have your function in place, you will have the entire machinery of the InsertArg script pushing your function through and applying it to the chat output of other scripts. How good are you going to feel when you have your function in place and can tell users they can start using it and here are the arguments availalbe? Pretty darn good. One last thought, remember to namespace your functions so as to not litter the global namespace. For an example of that, again, look at the core library script. And post back with any questions!
1598549299

Edited 1598572092
timmaugh
Pro
API Scripter
Video Check out this video I recorded giving an intro to the InsertArg and Xray scripts, and showing how to use them to build your output. (I apologize that I had to speak quietly, but that's what happens when your office is directly beneath your family members' bedrooms and you have to record this at night.) =D I wanted to show that you don't have to be able to code a complex line (and certainly not all at once!) to use InsertArg. You can build statements in parts and then put them together. New Version Also, I released a new version of all of the scripts (noted in the first post's Change Log), where I introduced more features like... ...an 'lve' output option (list value external) for elem row output for menus ...a sfxrlbl argument for getrepeating output to use a sub attribute as the button label (covered in the video) The new release also corrects some bugs I caught along the way.
1598627931
timmaugh
Pro
API Scripter
As of version 1.2 (both InsertArg and Core Library), the filter, format, and output options have been consolidated to the main script and exposed from there so that 3rd party functions can make use of the same structures as existing core library functions.
1598969345

Edited 1599241227
timmaugh
Pro
API Scripter
1.3 Version Updates Version 1.3 added the ability to create read-to-chat buttons. Originally, buttons that would read text from an attribute or ability would whisper the output. Now you can have them openly sent to chat. You access this in the output parameter (op), using 'b' for button, and 'r' for whispered reading or 'R' for chat reading. A lowercase 'e' is also available to use external labels if you are doing line-item output (row = elem in a menu). The application of that is discussed in this thread . Video I made another video showing how to build those 'readout' menus. Check it out if you need help unlocking how to build IA calls, and post back if you still need more explanation.
1599241210

Edited 1599653158
timmaugh
Pro
API Scripter
1.4 Version Updates Version 1.4 (including Core Library v1.4) added the ability for card output for repeating sections. This lets the user collect a number of different sub-attributes from an element in a repeating section (i.e., component parts of spell information from a spell in the list of spells) and output them to whisper or chat. This better solves the problem first mentioned in the 1.3 update post. This update also included a list (l) argument for the getrepeating function to allow for quicker, static declaration of a list of elements to include. Fuller discussion of these features can be found in this post . The image shows a call that produced buttons which will, when clicked, produce "card" output for the indicated repeating element. The buttons could have been included in a menu, if desired...
1599661820
timmaugh
Pro
API Scripter
1.5 Version Updates Version 1.5 (Core Engine) and Version 1.5 (Core Library) added a few enhancements around the ability to output cards for a repeating element... like... 1) full-width fields -- add the suffix '(fw)' to the end of the label for that sub-attribute on the card to have the output formatted to be full width. For instance, in the following sfxlist argument example the sub-attribute "spell_description" is re-labelled to be "Description", and the content would be displayed as a full-width row: !!sfxlist#+|magic_school School+spell_points Spell Points+spell_description Description(fw) 2) the ability to suppress "no [objects] found" messages for getrepeating, getattrs, and getabils. The default setting is for the script to alert you (via a whispered message) that you didn't return anything for a call to the just mentioned functions. This can be overridden using the argument 'emptyok'. Include that argument in the function's arguments (it doesn't require a value) to suppress these messages: getrepeating{{     !!c#@{selected|token_id}     !!s#spells     !!sfxn#spell_name     !!op#bC      !!emptyok     !!sfxlist#+|magic_school School+spell_points Spell Points+spell_description Description(fw) }} 3) a small formatting change to put the name of the repeating element (designated by the sfxn argument) into the title of the card Here is what a finalized card might look like with these changes applied: 4) a minor bug fix where the GM had not been able to create a global config handout 5) getselected now can return a token's id, or any of the other fields normally accessed via a "get" statement such as: name, represents, etc. This is under the 'r' argument
1599672559
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Spell Reporter After much video watching and help and encouragement from timmaugh, I have created an InsertArg menu, ready to drop into any game running the D&D 5th Edition by Roll20 Sheet. I've also included a brief css entry for display, but this is optional. Mostly it reduces the buttons to inline text links. This macro creates a spell list for the selected character. The list is presented as a whispered menu, and clicking on any spell will output a spellcard to chat, useful for presenting details of a spell during the game, regardless of the spells settings, and without triggering the usage of a spell slot. Thince there are html replacements in the macro, I would suggest saving this as an ability on a  Macro Character Sheet , otherwise they will revert if you open the macro to edit. The Code The CSS (optional, install in the user css file): <!-- BEGIN CONFIG --> --bg##ce0f69 --css#align:left; background-color: transparent; padding: 0px; color: #ce0f69; display: inline-block; border: none; <!-- END CONFIG --> Screen shots: The Menu The Spell Card
1599679787
timmaugh
Pro
API Scripter
That is sooo cool, Keith! Thank you for staying with (and seeing the benefit of) the script! I know there is a bit of a learning curve, but hopefully things started to click as you spent more time in it. It's awesome to see the idea realized to this extent, and in a system which I don't typically play/know... to see the principles working even though the environment is different!
1600310423

Edited 1600310777
timmaugh
Pro
API Scripter
Version Update - XRay v1.2 XRay is now even MORE useful to you, providing you even more information for purposes beyond just InsertArg calls. I've added fuller information about repeating sections. Now the header of a section xray includes the beginning portion of the name (repeating_section_rowID), as well as the sheet position number, so you can keep track of which element you are examining. I also added an "Info" button for each sub-attribute to output a chat message that contains the sub-attribute's rowID-based name, its sheet-position based name, as well as its individual object ID (I've never seen a need for the ID, but then again, I've never seen a script that provides it to you either, soooooo). =D Here's what that looks like... If you have an idea or suggestion for more information to interrogate from the sheet, let me know and I'll see if I can make it happen. (A big thanks to The Aaron for lending me his row-disambiguator scriptlet! It let me translate repeating element names into their position on the character sheet, making for a more intuitive and useful final product.)
1600313262
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The ID is useful if you build a macro and then later decide to re-order elements. $0 is a relative locator. The ID number is absolute. The Token Action Maker script uses IDs to build action buttons.
1600313949

Edited 1600314277
timmaugh
Pro
API Scripter
But are you talking about the rowID version, there? Or the ID? They're both absolute... I just have never seen a script use the ID of a sub-attribute (only the rowID version). (I know you know what you're talking about; I'm more wondering if I'm using the wrong terminology)
One example where it's useful: The DnD5e character sheet has 2 named resources (class_resource, other_resource) and then a repeating resource list of pairs of resources following. The resource pairs on the repeating resource list are named "resource_left" and "resource_right", so searching for resources on that sheet involves check the repeating row, and then check both the _left and _right resources. When ammo tracking is turned on for the sheet, the actual resource values (number of arrow/potions/Fernian embers/etc) get linked to the weapon; this uses the "sub-attribute" you talk about above. Reloading arrows just got simpler. Of course, in this specific case, you could also figure out the ID based on parsing the ammunition field of the attack. Simple access to the ID could enable ammunition tracking without the OGL helper, or allow spell component tracking, or any resource tracking on that sheet without remembering if a particular character uses left or right for any particular resource. There's probably other examples on other sheets.
1600334807
GiGs
Pro
Sheet Author
API Scripter
Fluffy5789 said: There's probably other examples on other sheets. There might be, but it's more likely the nature of the 5e sheet (whose idiosyncratic attribute structure has been criticised before) that makes knowing the individual attribute id useful. If you're using the API, you can (and usually have to) generate the attribute id yourself. If you're not using the API, it's just not useful to you, and the row_id version (especially with the simple $0 syntax) is convenient.
1600350903
timmaugh
Pro
API Scripter
That must be some of the oddities of multiple repeating sections that Keith mentioned as we were working through the card output functionality. If I'm understanding the way the sheet/system works, left entry #1 is paired with right entry #1, left-#2 with right-#2, etc., and those pairings are the end of the information required for that "thing"? Those are discrete and complete data? (I'm looking for ways to enhance the ways IA can interact with sheets...)
1600352029
GiGs
Pro
Sheet Author
API Scripter
I'm not familiar enough with the 5e sheet to say how those left and right entries are used, but they are attributes on the same row of the repeating section. I assumed they have no relation to each other, but are done this way because the sheet designer wanted a 2-column wide repeating section, a different item in each column, and didnt use the reporder/repitem classes to be able to force a repeating section into 2 columns. (Which is understandable - it's a bit trickier than just duplicating the items in a row to create the appearance of a left column and a right column.)
1601301391
Andreas J.
Forum Champion
Sheet Author
Translator
So this is a very powerful/flexible API for looking up stats and generating macros and such, but it doesn't have the capability to change stats, nor does it have any direct support to API that can, such as ChatSetAttr? Anyone would have an example of combining the use of InsertArg/Xray & ChatSetAttr?
1601325018
timmaugh
Pro
API Scripter
I will try to show an example of that. First, I have to install and learn ChatSetAttr... unless you can post an example call to ChatSetAttr and break down what the arguments are for? Then I could probably apply IA to give you a better idea of what it can do.
Hey, this script is kind of mindblowing. I am working on a macro that I think could benefit from this script, but I can't seem to get it to do the right thing. Maybe you could tell me if this is within the capabilities of InsertArg. So I want to call the "Curse" macro on my character "Alastar". But, I want to pass, as a hook, the value of calling the built in getattr function on another character (the one represented by the @{target} token). The attribute is called "cursed". I can not use the regular attribute syntax of @{target|cursed} because this attribute may not exist on the target. Does this make any sense at all? I'll try to write out what I _think_ the command would look like, but of course this isn't valid. !ia --whisper#Alastar|Curse --iscursed# This is the hard part. How do I call this function not on Alastar, but on @{target|token_id}? getattr{{!!a#cursed}} And then I have a second, more nooby question. The post says that I can pass "any of a character id, character name, or token id provided the token represents a character" for the first arg. But I am having trouble with this command: !ia --whisper#@{target|token_id} --show#hello From what I understand, this should just whisper "hello" in chat. But no matter which token I target, I get the "Unknown Source. Could not find ___" error. Am I missing something extremely basic?
Ah, I fixed it by using getAttr s instead of getAttr, which seems to support specifying a different character ID. It is kind of confusing that these two very similar functions have totally different APIs
1602734031
timmaugh
Pro
API Scripter
Hey, Ian... sorry I was slammed for a few days there and lost track of the board. Just checking in to see if you got both of your questions answered. I think you settled your first question by using getattrs instead of getattr... As for your second question, you are passing a token id to the second part of the whisper argument. That part of the whisper argument is designating your source for the command line, so you can't just pass a character reference... you have to pass the ability, too. Here's the original documentation from the first post: --chat#Cake            ==>    if speaking as a player, this finds an ability on that character's sheet called 'Cake' --chat#macro|orDeath   ==>    this finds a macro named 'orDeath' --chat#Izzard|Cake     ==>    this finds an ability named 'Cake' on the character 'Izzard' So you would need something like: !ia --whisper#@{target|token_id}|Curse I think that should work. But a second problem is that your default "show" hook isn't really there for you to do a replace operation on (replacing with "hello"). You only get that default hook if you don't supply a source for the command line. A better test in this case would be to use the special hook "cmd", which represents the entirety of the command line: --cmd#hello Post back if you're still having a problem, or if this isn't clear!
"We're outta cake!"
1602883461
timmaugh
Pro
API Scripter
So, my choice is, "Or Death"? =D
1607056517

Edited 1607060678
timmaugh
Pro
API Scripter
Version 1.5.1 - Minor Fixes This version includes v1.5.1 of the Core Engine and v1.5.1 of the Core Library. Both components had minor errors that required correction: Core Engine : loading a command line into an ability that did not yet exist on the character sheet               would break the sandbox; this has been corrected Core Library: utilizing buttons in an element row as the output for abilities (op#be) would encode               them using the attribute-referencing @ rather than the ability-referencing %; this has been corrected Cool Trick: Copy From Another Character You can use InsertArg to copy an ability from one character to another. This can be handy if, for instance, you have a "macro mule" character storing various scripts you want readily available to new tokens, characters, etc. Here's an example... For a mule character named "Mule" and a destination character named Calypso, the following line would copy the ability named "PrimaryWeaponCheck" from Mule to Calypso (chatting as Calypso): !ia --load#Mule|PrimaryWeaponCheck --store#PrimaryWeaponCheck Obviously, you must have edit rights to both. Alternately, you could replace the explicit call to Mule with an @{Selected} or @{Target} reference, or even with a ?{Source character} query so long as they return the name of the character in question . !ia --load#@{Selected|character_name}|PrimaryWeaponCheck --store#PrimaryWeaponCheck !ia --load#@{Target|character_name}|PrimaryWeaponCheck --store#PrimaryWeaponCheck !ia --load#?{Source character|Mule 1 |Mule 2 [|...Mule N ] |PrimaryWeaponCheck --store#PrimaryWeaponCheck
I was intrigued by that Ability copy trick, but I'm having trouble getting it to work at all. I have a macro mule character named 'compendium' and am using the following macro to copy an ability from 'compendium' to a player character, and I have that player character set as my Speaking As. !ia --load#compendium|?{Choose Macro} --store#?{Choose Macro} When I use the macro and type the name of the ability I want to copy, nothing happens. No error, no crash, and no copied ability on the character. Any step or detail that I'm missing?
1612157005

Edited 1612157045
timmaugh
Pro
API Scripter
Hi, Persephone... After the load command do you see the "Ability is loaded and ready" message box in chat? If not, you might not have the script installed and/or enabled. If you run !ia from the chat input, you should get the InserArt config menu. If you *do* get the IA menu from typing just !ia, then the next thing I would check is that you have Controlled By rights to the target character (explicitly granted to you, more than just "All Players"). If after that you're still unable to see the Ability copy to the target character, obviously make sure that it isn't open and "stale". Close it and reopen after the copy to make it refresh. You *should* see it. Here is a quick video I recorded of walking through your scenario: <a href="https://youtu.be/VvhH_GLCIb0" rel="nofollow">https://youtu.be/VvhH_GLCIb0</a> Let me know if this still doesn't work for you.
1612167165

Edited 1612167707
Hi, timmaugh, thanks for the response and the concise video! I do get the IA menu from the !ia command, but when I use the macro I do not see the Command Loaded message. The character sheet is closed, I have the character set as my Speaking As, and I made sure to put myself specifically in control of both that character and the source character. Nothing happens regardless of which ability I try to copy from the source character. I got it to work once on a test character that had only me as the controller, and I saw the Command Loaded message. Then I tried it on one of my player's characters after removing them from the Controlled By and putting only myself. That time, the ability was copied but there was no message in chat to confirm it. I tried again on the same character with another ability and now it's back to doing nothing. I tried again on the test character and I got a Command Loaded message but the ability was not actually copied. Third try with that character gave the message and copied it correctly. Fourth try again gave a message but didn't copy. EDIT: as an extra test, I tried copying an ability that didn't exist on the source character and it gave a chat message reporting that the ability doesn't exist, so it must be finding the abilities that do exist even when it doesn't copy them or give the Command Loaded message.
1612167890

Edited 1612172105
Okay seems like part of the issue may have been because one of the abilities I tested shared its name with a macro. This was one of the abilities that gave the message in chat but didn't actually copy. Trying to copy it again after deleting the macro worked. Still having trouble with others. UPDATE: I kept testing on other characters and eventually it seems I've got it working, though for some reason I don't get messages in chat when copying to some characters. Haven't found a reason, but the abilities are getting copied. Thanks again for your help! This feature alone is gonna make setting up my games ten times easier, I hope to dig in to the rest of the script soon.
1612186086
timmaugh
Pro
API Scripter
Glad you got it working! I'm not sure why you weren't getting the message every time, but I should share that if the designated ability is already present on the target character's sheet, then IA should overwrite the contents. That's what it does with the InsertArg ability -- that ability is created specifically as a transient landing place for command lines you're not explicitly telling IA to "load" somewhere else. If you keep noticing no-message or no-copy, let me know and maybe throw me an invite. I'll see if I can figure out what is going on.
1616215641

Edited 1616215722
Hey timmaugh, I've been getting the following error in chat when trying to load one specific ability from a source character to any other character: TypeError: h is undefined No console crash or errors. I'm able to load all other abilities to other characters so far. Any idea what this error is referring to? For reference, here's the command that's producing the error (changing the content of the ability makes no difference): Changing the number of ^s in the ability name makes it work, as does using a different emoji or adding a second one. For some reason it's the specific combination of ^^[tool-emoji] that's causing the error. Changing it to one of these works: And the commands for these similarly named but separate abilities work:
1616253388
timmaugh
Pro
API Scripter
I don't think that is InsertArg throwing that message, since it is hitting the chat like that. That sounds more like the same kind of Roll20 report when it doesn't find an attribute. I'm still trying to understand what it could be more specifically -- what's different about that emoji that the R20 parser doesn't like -- so I'm asking around to see if we can get an answer.
1616262009
timmaugh
Pro
API Scripter
I ran some tests, and I can't recreate that. I'm using Chrome on a Windows machine. If the compendium character has an ability named "🛠️" and I run the command: !ia --load#compendium|🛠️ --store#🛠️ That works. The carets (^^) are used elsewhere in IA to influence how a hook is consumed, but that shouldn't affect these commands, so in that case I wondered if your ability was actually named "^^🛠️". I made that change and tried again (with the carets in the command line), and that worked, too. As long as the ability is named what you're calling it, the emoji doesn't seem to matter. When I reference an ability that isn't there, I get the typical "UNKNOWN SOURCE" message announcing that IA couldn't find that Ability name. I'm thinking this is browser and/or R20 being buggy.
1616265869

Edited 1616265885
Thanks for looking into that! It's really odd. I have about a dozen attributes that I do this with, all of them with names that have a number of carets followed by one or more emojis (the carets force alphabetical order in the token action bar), but this is the only one that produces the error. I even tried deleting then recreating it, or changing the content of the ability to a single word in case the syntax within was somehow causing the error, neither of these worked tho. Only thing I haven't tried yet is loading an ability with that specific name from a character with a different name.. I'll try that later and see if that makes a difference.
1616606474

Edited 1616606592
So I've just started playing around with this and the rest of the meta scripts you've got which looks amazing.&nbsp; But, you know me, I've got questions and issues.&nbsp; Here is what I'm trying to do: I want to integrate this with Scriptcards to simplify and streamline all the macros for my players. I am currently testing the following macro !scriptcard {{&nbsp; &nbsp; --#whisper|GM &nbsp; --#title|Character stat check &nbsp; --+Rolling|Character rolls a stat check. &nbsp; --=Roll|1d20&nbsp; &nbsp; --+Result|Character has rolled a [$Roll] for their stat check.&nbsp; &nbsp; --? [$Roll] -ge 10|Success &nbsp; --+Failed|Character has failed their stat check.&nbsp; &nbsp; --X| &nbsp; --:Success| &nbsp; --+Success|Character has succeeded their stat check.&nbsp; }} So I would like every player to use this macro for every stat with the InsertArg command !ia --chat#GenericMacro|AbilityCheck --Character#getme{{!!r#n}} --stat#?{Attribute|Strength|Dexterity|Constitution|Intelligence|Wisdom|Charisma} Which works great!! The train goes off the rails though when I change the ScriptCard macro to include an attribute call like&nbsp;&nbsp; --=Roll|1d20 + @{Character|stat_modifier}. I was hoping that the commands that replaced the regular hooks (character, stat) with the appropriate replacements would do the trick. However, it doesn't do anything.&nbsp; So then I thought I needed to format the call in the macro with an added efrmt command --efrmt#rslv#getme{{!!r#n}}|?{Attribute}#@{getme{{!!r#n}}|?{Attribute}} But that gives me a&nbsp; TypeError: Cannot read property 'substring' of undefined So obviously I am either doing something wrong, misunderstanding the added attribute calls, or (most likely) both. :) Additionally, I would plan to use target calls, like @{target|bar2}, in these macros as well. Would those work as well in the same fashion I hope the attribute calls would? Any advice or help would be greatly appreciated.&nbsp;