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

To Roll20 Devs: Please Please Please Allow Selections Across Pages

I am a long time GM and mod developer. I recently have been working on a new mod called Gaslight. It is working right now, but it would be working so much better if SelectManager could pass selected tokens across pages. Here is the scenario: I run a command using SelectManager to select tokens A, B, and C. The command fires, but because A, B, and C are all on a different page than the one that I am currently looking at, the command's msg.selected contains no tokens, causing it to fail. I set a timeout to run the same command after 10 seconds, and during that time, I switch to the page that contains tokens A, B, and C. The command fires and runs fine because A, B, and C are on the same page as me. I assume that this is a limitation of Roll20's environment and not a limitation of SelectManager, since tokens I select on other pages manually become un-selected when I transition to a different page. I don't ask for much, but this one thing would really make my day. Please, please, please, and thank you.
The Mod sandbox itself can affect tokens on any page. Tokens are graphic objects with a _pageid, and a script can modify them by ID whether or not that page is currently being viewed. The limitation here is msg.selected, not the sandbox’s reach. msg.selected comes from the VTT client’s current selection at the moment the chat command is sent, and Roll20 selection is page-local. If the GM is on a different page, tokens on another page are not part of the current client selection, so they won’t be in msg.selected. Gaslight can use stored token IDs, explicit token IDs, page ID plus criteria, character IDs, or some other way to find the intended graphics without depending on msg.selected. If you post your code (or a link to it in Github or elsewhere) then others could help with troubleshooting.
Gaslight is operating as a meta-script. I know I can store my own token IDs. The point is that I wish to utilize SelectManager to inject tokens from other pages into msg.selected for other API. I know that this is a limitation of the VTT client. This is why the title of the thread starts with "To Roll20 Devs:" . What I am asking for is NOT something that can be fixed with plugin API.
A Roll20 mod can store a cross-page “virtual selection” in state , then later use that stored list to run actions against those tokens. A workable pattern would be: state.GaslightSelections = {   mySelectionName: [     { _id: tokenAId, _type: 'graphic' },     { _id: tokenBId, _type: 'graphic' },     { _id: tokenCId, _type: 'graphic' }   ] }; It would not be a real VTT selection, and it would not visually select tokens across pages. But it could absolutely behave like a selection for other API scripts , as long as those scripts rely on msg.selected and can operate on token IDs that exist on other pages.
Right. So for API scripts that can run against IDs, there's no problem here. I've already got that figured out. The issue is that I'd like to have this work seamlessly for API scripts that run against selected tokens only. And in those cases, I need to be able to inject tokens into msg.selected.
1781809806

Edited 1781809840
Gauss
Forum Champion
Kenan Millet said: &nbsp;This is why the title of the thread starts with "To Roll20 Devs:" .&nbsp; If you want to contact the Devs the forums are not the best place for that. The forums are primarily community help. The Devs may or may not see it.&nbsp; File a Help Center request or a Suggestion in the Suggestions forum.&nbsp; Help Center:&nbsp; <a href="https://help.roll20.net/hc/requests/new" rel="nofollow">https://help.roll20.net/hc/requests/new</a> Suggestions forum:&nbsp; <a href="https://app.roll20.net/forum/category/385189" rel="nofollow">https://app.roll20.net/forum/category/385189</a>
1781897947
timmaugh
Pro
API Scripter
(Author of SelectManager and the other metascripts in the Metascript Toolbox)... Fetch can refer to tokens from another page in a couple of ways: 1) if the token is uniquely named across the game (I know, not always ideal), and 2) by using a page reference in the Fetch construction (new in 2.2.1) Because you're using a Fetch construction to return the token, and Fetch typically runs AFTER SelectManager (if you have the full Toolbox installed), you either need to defer the SelectManager construction: {\&amp;select ... } ...or you could use a ZeroFrame timing structure to reorder Fetch to be first: {&amp;0 fetch, sm} Also, if you are operating as a metascript but (it would seem) *not* registering to ZeroFrame, you'll need to make sure your script is installed *after* the Toolbox to make sure that the Toolbox can do all of the above before your script sees the message. (Seems like you're doing that already, but figured I'd say it.) If you have any trouble utilizing or building the new functionality of Fetch and making it work with SelectManager, post back.
1781921804
timmaugh
Pro
API Scripter
To make it clear... Fetch does not require the unique name to reference a token from another page if you're using the method from update 2.2.1 (linked). Those were 2 different methods of referring to a token from another page.
I guess I'm a bit confused @timmaugh. Are you suggesting that I can pass token IDs to SelectManager in a command, and it WILL add them to the msg.selected for that command even if the token IDs that I pass to SelectManager are on a different page? Because the behavior that I am seeing is that it only worked when I was on the same page as the tokens that I passed to SelectManager.
1782064395

Edited 1782227673
timmaugh
Pro
API Scripter
Kenan Millet said: I guess I'm a bit confused @timmaugh. Are you suggesting that I can pass token IDs to SelectManager in a command, and it WILL add them to the msg.selected for that command even if the token IDs that I pass to SelectManager are on a different page? Sooooo... you're right. I just double checked and while I'd opened up Fetch to retrieve token information via page reference, I hadn't opened up SelectManager to do the same thing. So while Fetch *would* retrieve the token ID from another page for you, SelectManager was losing that token because it didn't cmoe from the same page that you were on. But I just fixed that in SelectManager and pushed the update into the merge queue.&nbsp;Once that propagates down to your game, you should be able to EITHER: use a Fetch construction with a page reference to retrieve the token ID, i.e., @(Bob[Token Library Page].token_id), ...or... use an explicit token ID you've pre-retrieved from another page and hard-coded into your command line If you need it before the merge happens, you can modify SelectManager yourself. Change line 234 to: &nbsp; &nbsp; &nbsp; &nbsp; let alltokens = [...findObjs({ type: 'graphic', id: query }), ...findObjs({ type: 'graphic', pageid: pageid }), ...findObjs({ type: 'text', pageid: pageid }), ...findObjs({ type: 'path', pageid: pageid })] Obviously the second option is more static (having to pre-retrieve the token IDs from whatever page they are on), however it does come with the benefit of not having to defer other constructions that would establish or rely on the token being in the msg.selected array. The opposite is true for the first option... it offers the benefit of being more "run time" oriented (you don't have to know what the ID is of the "Bob" token on the "Token Library Page" page, you just need to know that you HAVE a token named "Bob" on the "Token Library Page" page), but it *will* require the deferrals. For instance, since Fetch runs after SelectManager (by default), you'd have to defer the "select" construction that would be establishing the select tokens: {\&amp;select @(Bob[Token Library Page].token_id) } And if you were going to retrieve any information from the selected token in the command line, it would have to be deferred to wait until SelectManager had a chance to select the token: @\(select.hp) All together, that could look like: !The HP of @\(selected.name) is @\(selected.hp). {&amp;simple} {\&amp;select @(Bob[Token Library Page].token_id) } A quick explanation... In any metascript loop, SelectManager will run before Fetch: Loop N SelectManager Fetch Since SelectManager will rely on Fetch having returned the ID of the token to select, we can't let it try to find the token before the Fetch construction has resolved. That means we defer it (to hide the construction), and our work proceeds as: Loop 1 SelectManager - no work detected Fetch - retrieve ID of Bob from "Token Library Page" Loop 2 SelectManager - select the token with the ID (as returned by Fetch in Loop 1) Fetch - retrieve info from the currently selected token (as selected by SelectManager in Loop 2)
For some reason, I had assumed that cross-page selections in SelectManager wasn't a thing because of a limitation of Roll20. Good to know that it is fixed now.
1782227936
timmaugh
Pro
API Scripter
Annnd... two mistakes I only just now caught: 1) the file I changed locally (for v1.1.14) didn't include the updated change. So this merge will change your version number, but nothing more. 2) in my previous post sharing the line to update if you want the change more immediately than waiting for the merge, I copy/pasted from the file that didn't have the change. So even if you update line 234 to match what I posted, it won't work. *facepalm* I have updated the previous post to now have the correct verbiage, and I will include it here, as well (since now it will be another week before the ACTUAL fix goes through the merge process: &nbsp; &nbsp; &nbsp; &nbsp; let alltokens = [...findObjs({ type: 'graphic', id: query }), ...findObjs({ type: 'graphic', pageid: pageid }), ...findObjs({ type: 'text', pageid: pageid }), ...findObjs({ type: 'path', pageid: pageid })] I am adding the *correct* file to next week's merge queue, so it will be coming soon. My apologies for the mistake.
Hi timmaugh. Fetch API keeps crashing my API sandbox. When I disable Fetch, the sandbox loads ok. But with Fetch active, I get a 1-click error...
@timmaugh I'm also noticing that when I run a command with Fetch API from within the chat window, it seems to work, but when I run the same command through sendChat in the plugin, it doesn't work. Here, I am sending a command through the API, and then sending that same command manually through the chat. "Gaslight Sending token-mod command through sendChat function: '!token-mod --ids -Ow-8S_XCfAX1Rbzz7Er --set name|@(-Ow-68aVwkxwg0CclWYI.passive_wisdom)'" "Gaslight [DEBUG token-mod command received]: !token-mod --ids -Ow-8S_XCfAX1Rbzz7Er --set name|" "Gaslight [DEBUG token-mod command received]: !token-mod --ids -Ow-8S_XCfAX1Rbzz7Er --set name|18"
1782466832
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Hi Kenyan Millet! the API cannot send API chat commands directly. There’s no “player” to send them. They will silently fail. Unless you wire a call directly to the other script, your best bet is to send it to chat as a clickable link.&nbsp;
Sure, but I've been sending all sorts of chat commands "indirectly" by calling something like this and they work just fine with the one exception regarding Fetch specifically: sendChat('player|' + msg.playerid, "!token-mod --ids -Ow-8S_XCfAX1Rbzz7Er --set name|@(-Ow-68aVwkxwg0CclWYI.passive_wisdom)");
1782491829
The Aaron
Roll20 Production Team
API Scripter
Regrading sending API commands to chat, it does work, but scripts often start off by trying to find the player from the playerid property in the message.&nbsp; For a message sent by the API, the player id will be "API", which means a lookup for player will fail which often causes the API script to stop processing the command.&nbsp;&nbsp; TokenMod has this code: let who=(getObj('player',msg_orig.playerid)||{get:()=&gt;'API'}).get('_displayname'); In the case that player lookup fails, it assumes it was from and API script.&nbsp; Probably if I wrote it again, I'd check for 'API'===msg_orig.playerid instead, but either way works.
What has me a bit confused I guess is why this would cause specifically Fetch to fail and nothing else. SelectManager works fine, TokenMod itself works fine, APILogic works fine. Just Fetch fails.
1782507800

Edited 1782508089
The Aaron
Roll20 Production Team
API Scripter
I don't know what specific event is being responded to, but the crash in the screenshot above is because there are no objects of the type that is being looked for on line 986 in Fetch, so it gets an undefined object and attempts to access the `id` property on it, which results in the crash.&nbsp; Looks like buildPropContainers() is finding all the object types, then getting an instance of each one to read properties from.&nbsp; For some reason it finds a type, but then by the time it tries to grab an instance of that type it doesn't exist anymore.&nbsp; It could be that some script is creating a character (or some other object type) then removing it, and fetch finds the character when building its list, but then the character is gone by the time it tries to find an instance to read the properties from, but it isn't checking if the object exists before trying to read from it.&nbsp; Kind of an aberrant case.
1782522796
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The Aaron said: Regrading sending API commands to chat, it does work, but scripts often start off by trying to find the player from the playerid property in the message.&nbsp; For a message sent by the API, the player id will be "API", which means a lookup for player will fail which often causes the API script to stop processing the command.&nbsp;&nbsp; TokenMod has this code: let who=(getObj('player',msg_orig.playerid)||{get:()=&gt;'API'}).get('_displayname'); In the case that player lookup fails, it assumes it was from and API script.&nbsp; Probably if I wrote it again, I'd check for 'API'===msg_orig.playerid instead, but either way works. Thanks for the clarification, Aaron!
Yeah, that seems to be the problem for fetch. It looks like fetch doesn't work if playerid === "API". Bummer.
@timmaugh I don't suppose you would be willing to make it so Fetch syntax is respected when the playerid === 'API', would you?
1782564495
timmaugh
Pro
API Scripter
Let me check all of that (and thanks, Aaron, for running that down!).&nbsp; With the last update, fetch gained the ability to detect new game object types. In order to make that happen, the way it builds and knows about available properties had to be dynamic at the start. I did not envision a case where the object would exist only temporarily, but Aaron's explanation about a script potentially building a type, and then destroying it, makes sense. I will see if I can buffer against that, and while I'm in there working see if I can lift the limitation against player = API. If it doesn't work for whatever reason (permissions to certain objects are def player based - like handouts), one solution could be for me to give SelectManager an {&amp;as} tag: {&amp;as timmaugh} {&amp;as gm} The trick would be to control who can use that tag... Otherwise it would just be a backdoor for a player to get info you don't want them to have (like a handout).&nbsp;
1782565024

Edited 1782565279
As a (very) temporary measure (as I really am not a fan of the solution), I am doing the following: case '--script-lock': scripting = true; // WORKAROUND: API sendChat sets playerid='API', Fetch denies char access. // Temporarily enable playerscanids. TODO: remove when Fetch treats API as GM. if (state.Fetch &amp;&amp; state.Fetch.settings) { state[SCRIPT_NAME]._fetchPcidBackup = state.Fetch.settings.playerscanids; state.Fetch.settings.playerscanids = true; } return; case '--script-unlock': scripting = false; if (state.Fetch &amp;&amp; state.Fetch.settings &amp;&amp; state[SCRIPT_NAME].hasOwnProperty('_fetchPcidBackup')) { state.Fetch.settings.playerscanids = state[SCRIPT_NAME]._fetchPcidBackup; delete state[SCRIPT_NAME]._fetchPcidBackup; } return; When gaslight is about to run a script, it first sends a "--script-lock" command as chat followed by the script commands as chat, then a "--script-unlock" command. It receives them in the order they were received, and then processes them. At startup, I have it reset the Fetch setting as well in case of a sandbox crash: const checkInstall = () =&gt; { ensureState(); // Crash recovery: revert Fetch playerscanids if we crashed mid-script if (state[SCRIPT_NAME].hasOwnProperty('_fetchPcidBackup') &amp;&amp; state.Fetch &amp;&amp; state.Fetch.settings) { state.Fetch.settings.playerscanids = state[SCRIPT_NAME]._fetchPcidBackup; delete state[SCRIPT_NAME]._fetchPcidBackup; } createHelpHandout(); log('-=&gt; ' + SCRIPT_NAME + ' v' + SCRIPT_VERSION + ' Initialized &lt;=-'); checkDanglingGroups(); if (Object.keys(state[SCRIPT_NAME].activeGroups || {}).length &gt; 0) buildTriggerMap(); }; I know what I am doing is most certainly a faux pas (as meddling with other plugins' states sounds like crossing a line in some ways), but I really wanted to get this part working, and I think this solution does the minimal amount of fiddling with Fetch while still respecting player settings.
1782567842
timmaugh
Pro
API Scripter
Well, if that gets it working (I was going to investigate whether that state setting could fix things), then you can configure that in your game from the command line: !fetchconfig -playerscanids !fetchconfig +playerscanids I'll still investigate the crash, and the possibility of getting a special tag for SelectManager, but if this gets you up and running, it's better than having to graft in a temporary hack to your own code.
Is there a reason you can't simply allow playerid==='API' to work for Fetch attribute resolution?
1783296711
timmaugh
Pro
API Scripter
I am investigating that, but there are avenues of token-identification that are player dependent (do you want the "Shia LaBeouf Actual Cannibal" token from the "Legendary Fight" page or the "Normal Tuesday Night" page?... the token returned is the token on the page that the player is also on). But I do have a question... do you have SelectManager configured to give back the playerid? Run this command: !smconfig ...and see what the "playerid" option is set to: Make sure it's enabled. If it isn't, run this command: !smconfig +playerid (that turns it "on"... to turn it "off" you can swap that plus sign for a minus) The idea is the same as how SelectManager handles selected tokens, it can handle the "playerid" property and the "who" property of a message. It tracks the properties of the last message sent by a player, and applies them to any message that comes through without those properties (e.g., a script generated message with the playerid property set to "API"). So if the player initiates a message that triggers a script, the properties are tracked. If that triggered script then sends a message (which will have no selected tokens, and the "who" and "playerid" properties set to "API"), SelectManager will step in and restore the values you've enabled to have restored. Give it a try and let me know if it works for you.