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

API access/selecting repeating character sheet fields

I have a character sheet with multiple repeatables lists, like... `repeating_Rangedweapons`, `repeating_meleeweapons`, etc. When the user executes a "chat" command (that I'm picking up in `chat:message`), I'd like the be able to ask the user to pick a weapon from this list (e.g. from the `repeating_meleeweapons` list for example), and get back that weapon object, id, index... something I can identify the weapon with within the script and then pull the other attributes of that weapon from that sheet.  I have the character object in context.  I'm basically running... ` !bc --caller|@{selected|character_id} --target|@{target|character_id}` So I have the character who is "acting".  How do I have him pick a weapon from his sheet?
On a related note, it appears I can not use things like `getSectionIDs()` from inside a normal api script, only inside a sheet worker script.  How can I interrogate a character sheet from a normal API script?
Similarly, I also tried... var talents = findObjs({ type: 'attribute', characterid: selectedId, name: "repeating_Talents" }, {caseInsensitive: true}); Which did not work :).
1542275236

Edited 1542275428
GiGs
Pro
Sheet Author
API Scripter
Disclaimer: i'm a bit rusty, but I think this process should work. I cant promise this code is perfect though. You can use filterObjs, something like //assuming character = @{selected|character_id} let weaponIDs = filterObjs(function(obj) {     if(obj.get("type") === 'attribute' && obj.get('characterid') === character &&          obj.get('name').indexOf('repeating_meleeweapons') > -1 && obj.get('name').indexOf('_name') > -1 return true;     else return false; }); Note: this line is very important to get right: obj.get('name').indexOf('repeating_meleeweapons') > -1 && obj.get('name').indexOf('_name') > -1 return true; The first part should be the name of your repeating section, and the second one should be the weapon name field within that section. If I remember syntax properly, this will give you an array of attribute objects. They'll have names like repeating_meleeweapons_long id here_weapon_name You can then build a new object, something like weapons = []; weaponsIDs.forEach(key => {         weapons.push(getAttributeByName(character,key)) + ":" + key; }); weapons.sort(); This will give you an array that internally looks something like [     dagger: repeating_meleeweapons_different id here_weapon_name,     shortsword: repeating_meleeweapons_long id here_weapon_name,     longsword: repeating_meleeweapons_another id here_weapon_name, ] You can then build a dropdown to show the players, something like dropdown = '?{Pick a Weapon'; weapons.forEach(weapon => { let thisWeapon = weapon.split(":");     dropdown += '|' + thisWeapon[0] + ',' + thisWeapon[1]; }); dropdown += '}' If you print this dropdown to chat, players will be able to pick a weapon, and your script will get the weapons ID which you can then use however you need. Each weapon will have a name like this: repeating_meleeweapons_long id here_weapon_name That long id in the middl is the unique identifier for the row. So if you replace the "weapon_name" part at the end, with the other weapon stat names, you'll be able to access all the attributes within the row.
1542289656
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
GG's code is mostly the way to go. I've only got two recommendations; first would be to use findobjs to get the attributes of the character in question and then _.filter() them rather than using filterobjs. Findobjs has been optimized to use an indexed list of all objects in the game; if I remember correctly filterObjs has not. This makes filtering the results of findobjs more efficient than filtering through all objects in the game which would include tokens, pages, macros, characters, handouts, etc. Second is that instead of building a roll query, build a chat menu with a button for each choice. The user is going to have to click a button anyways so using the chat menu approach means less clicks.
1542329957

Edited 1542329981
I think I'm following what you are saying, but let me check 8) Given... < fieldset class = " repeating_Talents " > < div class = " sheet-item " style = " width : 90 % " > < input name = " attr_Talents " type = " text " > </ div > < div class = " sheet-item " style = " width : 10 % " > < input name = " attr_TalentsPage " type = " text " data-i18n-placeholder = " page-number-#-u " placeholder = " pg# " style = " text-align : center ; " > </ div > </ fieldset > The code to access the above entries for a given character would be... let weapons = findObjs({         type: 'attribute', characterid: character}); Will return a list of attributes for the character.  Which one would then filter... weapons = _.filter(weapons, _wp => {     if(_wp.get('name').indexOf('repeating_Talents') > -1 && _wp.get('name').indexOf('_name') > -1) return true;     return false; }); Which will then give me a list of objects... whose name includes `repeating_Talents` and `_name` ? I'm not 100% sure I appreciate the subtleties of what `get()` does.  That said how would I grab the `attr_Talents` and `attr_TalentsPage` for each object? The second part I think I get from something I was reading last night.  So if I build the dropdown text macro, I can send it to chat via... sendChat("", dropdown, (_cbMsg) => {     // _cbMsg.contents should contain the weapon selected's name...? });
Also, in the dropdown example, I can't send `?{Dropdown|...options}` via `sendChat` and have them actually generate a dropdown can I?
1542340879

Edited 1542340924
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Shin said: I think I'm following what you are saying, but let me check 8) Given... < fieldset class = " repeating_Talents " > < div class = " sheet-item " style = " width : 90 % " > < input name = " attr_Talents " type = " text " > </ div > < div class = " sheet-item " style = " width : 10 % " > < input name = " attr_TalentsPage " type = " text " data-i18n-placeholder = " page-number-#-u " placeholder = " pg# " style = " text-align : center ; " > </ div > </ fieldset > The code to access the above entries for a given character would be... let weapons = findObjs({         type: 'attribute', characterid: character}); Will return a list of attributes for the character.  Which one would then filter... weapons = _.filter(weapons, _wp => {     if(_wp.get('name').indexOf('repeating_Talents') > -1 && _wp.get('name').indexOf('_name') > -1) return true;     return false; }); Which will then give me a list of objects... whose name includes `repeating_Talents` and `_name` ? I'm not 100% sure I appreciate the subtleties of what `get()` does.  That said how would I grab the `attr_Talents` and `attr_TalentsPage` for each object? Yep, although looking at the filter code that G G provided, I might suggest one more change, although I don't know how it changes performance (very little if at all would be my guess). The change is to use regex and .test() instead of the more complicated .indexOf() code: weapons = _.filter(weapons, _wp => {     if(/repeating_talents_[^_]+_name/.test(_wp.get('name')){         return true;//This is just a style thing, but I prefer this if() syntax     }     return false; }); The second part I think I get from something I was reading last night.  So if I build the dropdown text macro, I can send it to chat via... sendChat("", dropdown, (_cbMsg) => {     // _cbMsg.contents should contain the weapon selected's name...? }); This part you figured out in your next post, but you can't directly send a roll template to chat from the API as it doesn't pop up for anyone. You instead have to wrap it in an API command button, which is why I recommended creating a chat menu of buttons, one button per option, instead.
Yeah, I have the character sheet inspection working programmatically, but not the menu option for players.  I know how to emit buttons easily enough, but more desirable would be to query from the script.  For the moment, as a work around, I bind the name of the weapon as desired as a param going into the api chat command and then find it programmatically using the tricks you two suggested above.  The goal was single click initiation Ideally, I'd actually like to be able to bring up some sort of multi-select box.  If you imagine a game like Battletech, for instance, where I might want to select all my medium lasers and my SRM6 to fire at the selected target...
1542356531
GiGs
Pro
Sheet Author
API Scripter
Is there a reason to query from the script? You could send a macro to chat, and have that launch a second script command.  Roll20 doesnt support multi-select boxes unfortunately, though people have faked it with chat menus.
1542359207

Edited 1542361886
Ah, that makes sense.  What does faking it with chat menus look like?
1542361371
GiGs
Pro
Sheet Author
API Scripter
It's a technique many script makers use for configuration. You can print html _CSS to chat, so what people do is print a table of labels + API chat buttons (which might be formatted to look like checkboxes, ticked or unticked, or buttons pressed or unpressed). The user clicks the button or checkbox, and the macro hidden behind that button is triggered, launching the script, changing a config setting and the script then reprints the full table with the clicked checkbox or button state changed. It leads to very messy chats, as you have the full table repeated several times, but it's the best we can do presently in roll20.
1542361546
GiGs
Pro
Sheet Author
API Scripter
For a good illustration, see any of Robin Kuiper's scripts. Lazy Calendar is a good one to test, since it doesnt depend on any character sheets. Install it and the config table will appear in chat, and you can click buttons and see what happens. In Robin's scripts IIRC, state of the buttons doesnt change, but it's not too hard to incorporate that - I just cant remember at this moment a good example to test of that.
Ah, I think I understand the mechanism.  I assume they use the ` {noarchive: true }` setting to minimize the impact.  I presume, regardless of css, things like `position` are ignored?
1542371862

Edited 1542372177
Ammo
Pro
I too went to the Robin Kuiper school of scrolling interfaces :). &nbsp; &nbsp;I collected my versions of all the styles in a base class here:&nbsp; <a href="https://github.com/derammo/der20/blob/master/src/der20/roll20/dialog.ts" rel="nofollow">https://github.com/derammo/der20/blob/master/src/der20/roll20/dialog.ts</a> The dialogs looks like this: &nbsp; <a href="https://derammo.github.io/der20/#rewards" rel="nofollow">https://derammo.github.io/der20/#rewards</a> I am sure you could emit all sorts of inline style to screw up the UI (I have not tried.) &nbsp;Please report back after you try it, sounds fun :) &nbsp; I just tried to do the minimal thing that would have a good chance to render on every device, including phones. &nbsp; Remember, we don't have the browser detection stuff available to us here, since we just emit hard coded inline CSS. &nbsp; Also, it is probably rude to try to CSS yourself out of the chat box container ;) If I wanted to make a "single click initiation" thing, then I would probably generate token action macros so you get buttons at the top. &nbsp;Then use those buttons to call your script with enough information to find the correct weapon(s) and put them into a collection in the script's state. &nbsp;Then hit another button to "fire." &nbsp; So you would have to click heavy laster, srm6, fire. &nbsp; The script has state, which macros can't do (other that gross hacks with the turn order tracker.) &nbsp; You can use that to track permanent configuration like most people do, but also to track things for a short while and then clean them up. &nbsp;&nbsp; For example, in the !rewards plugin, I have long-lived definitions about the game room stored as configuration state. &nbsp;When you play a game, you "instantiate" these definitions into another section of the state, which you then manipulate with the dialogs, and finally emit/use/discard. I think using just buttons and script state is cleaner for your application than just presenting a dialog with all the weapons and letting people check which ones you want, because you will be scrolling the living crap out of the chat box if you do that. &nbsp;You would be rendering that dialog at least 3 times in this example, just to fire your weapon. &nbsp;Using those dialogs for combat things isn't great for that reason. &nbsp;
Well, the main thing is we have no DOM access, so we can't manipulate, alter or update existing entities.&nbsp; Best I can envision would be effectively creating an overwrite panel, but I haven't tested to see if this is possible.&nbsp; I imagine other people have and found it wasn't, given the lack of usage of that technique :/.&nbsp; The calling back to yourself with questions to fill out a query is pretty good though.&nbsp; I imagine for the BT example I used, you could maintain a stateful list of weapons and query `?{Choose weapon to add to fire group|...}` which you built dynamically in a logical loop removing selected weapons each time until the player selected the "Done" option.&nbsp; I'll have to test that one too :). Thanks for all the help and advice, btw!&nbsp; GG, Scott C. and Ammo; you guys have made this effort possible 8).&nbsp;&nbsp;
1542410194

Edited 1542410239
GiGs
Pro
Sheet Author
API Scripter
Being able to update an existing panel or overwrite an existing one is impractical, because it goes in the chat, and any time anyone writes anything in chat, the panel would be pushed upwards and eventually off screen. One way to have a stable control panel type effect is used in that LazyCalendar script: dedicate a handout to your layout, and have your script update that. I don't know if the handout can respond to clicks though, you might still have to activate things through chat buttons, which then update the visible handout.
... chat buttons or macro buttons. &nbsp;If all you want is to show the weapon selection (by a query parameter with enumerated values) and another action to fire when you have finished adding weapons, you could do with just macro buttons and commands, and not display anything in the chat at all.
Not if it was position fixed in a div it wouldn't.&nbsp; But you'd have to do something ugly to consistently overwrite the panel, since you can't replace or remove it, like keep a z-index in your script and increment it with each update.&nbsp; Probably doesn't bare considering, really. I could set up token actions that would be like... !selectWeapon Medium Laser 1 !selectWeapon Medium Laser 2 etc. And then have each basically just add the weapon to a firegroup variable in the script to be triggered by a !fire token action, but there are some issues... You can't tell what weapons you've selected.&nbsp; You can't unselect them (unless I toggle, but that makes the lack of state display worse).&nbsp; You could try to cludge it, by using status markers to indicate weapons selected, but those are already being used for other purposes.&nbsp; Unless there is a method to display you are thinking of which I'm not?
BTW, I thought I understood you guys saying this should trigger a drop down menu, but it's not triggering for me... sendChat(msg.who, "!bctest --aim|&amp;#63;{Are you aiming&amp;#63;|No aim,0|Half,+10|Full,+20}" ); I've tried a few permutations of escapes.&nbsp; Did I misunderstand what you guys meant or am I missing a key replace?
1542415550

Edited 1542415665
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Reread my response above: Scott C. said: Shin said: The second part I think I get from something I was reading last night.&nbsp; So if I build the dropdown text macro, I can send it to chat via... sendChat("", dropdown, (_cbMsg) =&gt; { &nbsp; &nbsp; // _cbMsg.contents should contain the weapon selected's name...? }); This part you figured out in your next post, but you can't directly send a roll template to chat from the API as it doesn't pop up for anyone . You instead have to wrap it in an API command button , which is why I recommended creating a chat menu of buttons, one button per option, instead. There is no way to dynamically update something in the chat pane via the API. You could use the handout method Ammo linked to for a more dynamic method, but I think that part of the reason you wanted to code this script was to avoid having to open a large sheet sized dialog in game.
Yeah, I thought GG was saying you could do that.&nbsp; That's really a shame, because if it *did* work, you could get around a lot of issues that way.
1542419414
GiGs
Pro
Sheet Author
API Scripter
Sorry, no I wasnt saying you could do it. I was saying if &nbsp;you could, it would be impractical.&nbsp;
1542419946
GiGs
Pro
Sheet Author
API Scripter
Shin said: BTW, I thought I understood you guys saying this should trigger a drop down menu, but it's not triggering for me... sendChat(msg.who, "!bctest --aim|&amp;#63;{Are you aiming&amp;#63;|No aim,0|Half,+10|Full,+20}" ); I've tried a few permutations of escapes.&nbsp; Did I misunderstand what you guys meant or am I missing a key replace? Why are you using html entities there? IIRC you can use them to suppress dialogs appearing when you dont want them to, which is the opposite effect you are going for here. Maybe it'll work if you remove the html entities. You should only be using html entities when you need to nest queries. Also, I see a potential issue I've run across before. If you are using "|" as a separator, for collecting key and value pairs, queries use "|" so you could have issues. It's better to use a different separate character if interpreting queries, i usually use #. (Maybe it's not needed - the query might be resolved before the script sees it, just mentioning it just in case.) All that said, I question the value of using sendChat to collect inputs. The normal procedure in roll20 scripting is have the initial script call include all the query dropdowns you need in that initial command that launches the script, then you sort them into parameters within the script and do what you need with each in turn.
Well, you can't collect inputs with sendChat, because the drop down doesn't show up.&nbsp; If it *did*, then I could totally query up weapon selection and also query for any missing parameters neat as can be.&nbsp; Sadly, it appears you can't get queries to show up from an API generated sendChat message.&nbsp;&nbsp;
1542421220

Edited 1542421510
GiGs
Pro
Sheet Author
API Scripter
That's weird. I could have sworn I've used scripts to send macros to chat which include dropdowns before, but maybe I'm misremembering. Edit: it would explain why no one uses them that way, though! (Aside from also having to deal with async processing which is already a pain.)
1542424029

Edited 1542424105
Ammo
Pro
Oh wow, I am so sorry. &nbsp;I should have gone and tested before saying this would work. &nbsp;I had also remembered incorrectly. &nbsp;The query popups work in the link from a chat button, but not the other ways I have tried. &nbsp; There seem to be zero reasons why it doesn't work, except that it doesn't. &nbsp; Maybe the issue is that those popups are on the 'player' side of their server logic, and we are just pushing text to them from the other side, conceptually. Again, so sorry for giving bad advice. on ('ready', () =&gt; { // this creates an API command but does not resolve the query sendChat('hello Shin', '!testing ?{testing|1,one|2,two|3,three}'); // this prints the text but does not call the macro sendChat('hello Shin', '#indirect'); // this does stuff sendChat('hello Shin', '&lt;a href="!arm ?{weapon|1,one|2,two|3,three}"&gt;arm&lt;/a&gt;' + '&lt;a href="!fire"&gt;fire&lt;/a&gt;'); });
1542429760

Edited 1542429779
GiGs
Pro
Sheet Author
API Scripter
What if you give it a proper player_id in the first paremeter of sendChat? (Instead of 'hello Shin').
1542430626

Edited 1542431114
Ammo
Pro
Good question.. same result, except now I see my chat avatar next to it. on ('ready', () =&gt; { let justMe = `player|${findObjs({_type: 'player', _online: true})[0].id}`; // this creates an API command but does not resolve the query sendChat(justMe, '!testing ?{testing|1,one|2,two|3,three}'); // this prints the text but does not call the macro // NOTE: the macro contains the command we tried to run above sendChat(justMe, '#indirect'); // this does stuff sendChat(justMe, '&lt;a href="!arm ?{weapon|1,one|2,two|3,three}"&gt;arm&lt;/a&gt;' + '&lt;a href="!fire"&gt;fire&lt;/a&gt;'); // async to see if it will fill it in (NOPE) sendChat(justMe, '!testing ?{testing|1,one|2,two|3,three}', (messages) =&gt; { log(JSON.stringify(messages)); }) sendChat(justMe, '#indirect', (messages) =&gt; { log(JSON.stringify(messages)); }) });
1542430825
GiGs
Pro
Sheet Author
API Scripter
That's a shame. Thanks for doing the testing.
oops didn't see you already replied. updated with async tests
that is actually quite unfortunate. &nbsp; someone must have gone to some trouble to make async evaluation of rolls work, but not queries compare to this code:&nbsp; <a href="https://github.com/derammo/der20/blob/a0dfcca0a7a7570fb39fdf1f9860e5ddfadbfab4/src/der20/roll20/roll.ts#L6" rel="nofollow">https://github.com/derammo/der20/blob/a0dfcca0a7a7570fb39fdf1f9860e5ddfadbfab4/src/der20/roll20/roll.ts#L6</a>
1542431521

Edited 1542431571
GiGs
Pro
Sheet Author
API Scripter
That's a nice code snippet. I'll have to look through your other stuff, having it broken up into chunks like that might&nbsp;make it easy to pick out bits that are useful to me.&nbsp;
Ok I just had a good laugh. &nbsp; I was looking for something else, and I came across the implementation of all the query substitutions like ?{...} stuff. It turns out all that is client side. &nbsp;So it is no wonder we can't trigger it from the API :). &nbsp; Basically the &lt;A&gt; tags we present are never followed, which makes sense, because they aren't real links. &nbsp; Instead, &lt;A&gt; just has a click handler, which calls handleURL (client-side Javascript) which just forwards the URL to its chat input handler. &nbsp; That function unfortunately does all the substitutions and such on the client browser, so we will never be able to use it. &nbsp; If you want to read through it to see what is possible to do in a chat message, the function is&nbsp;doChatInput, which you can search for in the Web Inspector of your choice. &nbsp;It isn't obfuscated in any way. &nbsp;By the way, my sincere thanks for that, Roll20 people! &nbsp;I hate it when someone uses my CPU to run their scripts but then won't let me read them :)