We use Cookies to help personalize and improve Roll20. For more information on our use of non-essential Cookies, visit our Privacy Policy here.
Accept
Advertisement Create a free account

Question Regarding API Request Timing . . . Hunter's Mark Macro

1593282600

Edited 1593282677
Hello! I'm relatively new to most of this stuff, having only hopped on board with online play on Roll20 in March due to COVID-19. That being said, I feel I've learned a lot there is to know about macros, calling for abilities/attributes, rolls, templates, queries, etc. What I still struggle with is the black box known as the API. I understand the commands for the scripts I have installed, but I'm having trouble with the following (Scripts involved—ChatSetAttr, Token-Mod, CombatMaster). Sheet = D&D 5e by Roll20: !token-mod --set statusmarkers|-Marked --ids @{Vesper|mark_id} @{Vesper|wtype}&{template:spell} {{level=UA CFV: Favored Foe}} {{name=Hunter's Mark}} {{castingtime=1 bonus action}} {{range=90 feet}} {{target=A creature that you can see within range}} {{v=1}} 0 0 {{material=}} {{duration=Up to 1 hour}} {{description=You choose a creature you can see within range and mystically mark it as your quarry. Until the spell ends, you **deal an extra 1d6 damage to the target** whenever you hit it with a weapon attack, and you have **advantage on any Wisdom (Perception) or Wisdom (Survival) check you make to find it.** If the target drops to 0 hit points before this spell ends, **you can use a bonus action on a subsequent turn of yours to mark a new creature.**}} 0 {{innate=@{Vesper|wisdom_mod}/Long Rest}} {{concentration=0}} @{Vesper|charname_output} !setattr --name Vesper --mark_id|@{target|Target|token_id} !token-mod {{ --set statusmarkers|+Marked --ids @{target|Target|token_id} }} Here's the setup: I have CombatMaster installed and set up so that when the "Marked" marker is applied to a token, it toggles PC Vesper's global damage modifier $1 (hunter's mark; 1d6 piercing) ON. Upon removing said marker from any token, it does the opposite. This works great. What I'm trying to accomplish here is the following: 1. Remove "Marked" from @{Vesper|mark_id} (an attribute created to store the token ID of the marked creature) using TokenMod !token-mod --set statusmarkers|-Marked --ids @{Vesper|mark_id} This should "clear" the mark from the previous marked target so that there aren't multiple marks accumulating. Important that this fires off first, because when the "Marked" marker is removed from any token, it sets Vesper's global damage mod $1 to "off." 2. Spell description posted to chat 3. Set Vesper's @{mark_id} attribute equal to the new marked target's @{token_id} for storage. !setattr --name Vesper --mark_id|@{target|Target|token_id} No function except to prepare for step 1 should Vesper decide to move his mark to a new target. 4. Apply the "Marked" marker to the targeted token !token-mod {{ --set statusmarkers|+Marked --ids @{target|Target|token_id} }} This re-enables the global damage mod $1 (CMaster sees when "Marked" is added and turns Vesper's damage mod "on" again). The macro kind of  works. It doesn't seem to work if I use it several times quickly, and sometimes randomly doesn't work. I've tried a few variations of this, but from what I've learned per these forums and TheAaron's wisdom, it seems like multiple API requests in a macro aren't guaranteed to process in order. This is where I could use help solving this problem: 1. Is there a way I could make a one-click macro that is GUARANTEED to fire the above API commands off in order? If not . . . 2. Is there a simpler way I could achieve this effect such that it's guaranteed to work? With these scripts? Some other scripts? In short: apply "Marked" to new token, clear it from previous token (or all other tokens—we only use this marker for Hunter's Mark and Vesper is the only character with said spell). 3. Could this be solved with a custom script that calls these scripts? I don't know how to write scripts . . . If options 1 and 2 are exhausted, could someone help me with this possibility? Keeping it as simple as possible. 4. Though I hope it wouldn't come to this, perhaps a bottom-up script creation would be needed (rather than having a tiny script calling other big, mean scripts).  Please, oh scripting Wizards, give me your counsel on this matter.
1593286563
GiGs
Pro
Sheet Author
API Scripter
For async problems, whatever they may be, there's no easy solution. It's generally best to use just one script when you can, for changing related values. That saidm it's not clear the problem you're trying to solve here. When it doesnt work as intended, what part fails? It's not clear to me what the setattr command is achieving. The saved value is not actually used in this procedure, so when is it needed? As i said, I'm not sure what the problem you're having is, but once thing that raises a falg is the fact that combat master automatically switches your global damage modifier. That's fine if you're just using Combat Master, but if you misxing up several different scripts on the same task, it's a possible point of failure. If you're having sequencing issues, I'd disable that autosetting, and set it explicitly, using chatsetattr, so you are controlling when it gets set.
1593290384

Edited 1593290655
Thanks for the reply! Sorry i wasn't clear enough. Combatmaster is doing its part just fine. Its sole purpose (involved in this macro; it has other valuable purposes aside) is to tie together the presence of the Marked marker and Vesper's global damage mod $1. Said another way, it causes the addition or removal of "Marked" to automatically influence Vesper's global damage mod $1 appropriately. I haven't had it cause any problems in that regard, and it is actually the crux of the macro; I could disable it, but then fussing about adding and removing the markers is meaningless, because we would still have to manually toggle that modifier. As for the saving of the variable, it is intended to be referenced for any SUBSEQUENT uses of the macro, whereupon the previously marked token's token_id (read: the stored value of @{Vesper|mark_id}) has token marker "Marked" removed from it. Problems include: Not removing Marked from previous target Merely removing and reapplying Marked on previous target, instead of removing Marked on previous target and adding it to the current target (this might have happened in a previous version when I used @{Vesper|mark_id} on the last api call rather than re-using the @{target} call, I don't remember Goal is: one click macro; removes "Marked" from previously marked token (if any). Then applies "Marked" to targeted token. I don't mind if instead of removing "Marked" from the previous token, some macro/api help could be given to instead take the nuclear option and remove "Marked" from EVERY token before then applying "Marked" to the targeted token. "Marked" is only used in my campaign for this effect. Thank you again for your time spent in answering. Any advice or help you could offer is appreciated. Sorry if i sound curt, I'm typing this on my tiny phone now.
1593291328

Edited 1593291359
GiGs
Pro
Sheet Author
API Scripter
You dont sound curt. That's way more verbose than I am on a phone... It does sound like you're running into async issues. Using different script calls, there's no way to guarantee that those lines will run in the listed order, which can obviously mess up which markers get switched. I think the only reliable solution here is to do a dedicated script that handles all the parts.
1593306640
Thanks! With that in mind, is there a simple way for me to get started on making something in, what is the language we use here--Javascript?, that can just fire those API commands into chat in-order? With a single API command trigger? How do I learn said wizardry?
1593308000

Edited 1593308032
GiGs
Pro
Sheet Author
API Scripter
the API uses javascript, with some custom commands. You can find documentation at the wiki, the Objects page is the most helpful (but still pretty bewildering if you're new to it). I'll work up a quick proof of concept. Whats the sheet's global damage modifier attribute name? If you were using chatsetattr to set it, what name would you use? Also, what is the marked actual tag name? It'll likely be something like Marked::345 . To find out, install this script, add the marked status to a token, select that token, and type  !showtokenmarkers into chat. Post the result back.  on( "ready" , () => {      on( "chat:message" , msg => {          if (msg.content.split( " " )[ 0 ].toLowerCase() ===  '!showtokenmarkers' ) {              if   (!msg.selected)  return ;              if   (msg.selected[ 0 ]._type !==  "graphic" )  return ;              obj = getObj(msg.selected[ 0 ]._type, msg.selected[ 0 ]._id);              currentMarkers = obj.get( "statusmarkers" );              sendChat( "Token Markers" , currentMarkers);          }      }); });
1593308673
GiGs
Pro
Sheet Author
API Scripter
GiGs said: Whats the sheet's global damage modifier attribute name? If you were using chatsetattr to set it, what name would you use? To be specific here: what attributes do you have to set, and to what value, when activating the hunters mark to get that bonus damage?
1593311506
The id for "Marked" is:  Marked::2079759 The global damage modifier in question for character Vesper is: repeating_damagemod_$1_global_damage_active_flag (I use !setattr --name Vesper --silent --repeating_damagemod_$1_global_damage_active_flag|<1 or 0> to set the damage mod on or off, respectively) However, just to be clear, whenever  Marked::2079759  is added to a token, regardless of how, CombatMaster automatically runs the aforementioned API to turn Vesper's repeating_global_damagemod_$1 on, and removes it when it is removed from ANY token: I just want to make sure the thing wouldn't have any redundancies in it that could cause things to not work.  As far as attributes, based on the fact that with the CM setup in the screenshot, the damage mod is automatically turned on or of, I want the following things to happen, in this order: 1. remove  Marked::2079759  from a token whose @{token_id} is found stored in @{Vesper|mark_id}, if there is any token id stored there. This clears out "Marked" from previous Hunter's Mark target, and in doing so, CMaster automatically sets Vesper's repeating_damagemod_$1_global_damage_active_flag to 0. This functionality is not "intended" for the sake of this macro, per-se, it's just something I built in to CMaster to account for instances where hunters mark is removed manually, which will happen frequently, such as whenever combat ends. 2. Add  Marked::2079759  to @{target|Target|token_id} This sets Vesper's   repeating_damagemod_$1_global_damage_active_flag   to 1 automatically. 3. Save @{target|Target|token_id} to Vesper's attribute @{Vesper|mark_id} This serves no purpose except to set up step 1 of this cycle should the process be repeated. I hope that answers the questions. Again, the attribute is repeating_damagemod_$1_global_damage_active_flag, but as it stands I don't really want to break the functionality provided by CMaster and have that attribute manually toggled by this button. Simply adding  Marked::2079759  takes care of turning it on, and removing  Marked::2079759  turns it off. Thank you for your help!
1593317370

Edited 1593326202
GiGs
Pro
Sheet Author
API Scripter
Here's a script to try. Run it with !huntersmark @{target|token_id} If you target a character that already has a mark: it removes the mark and clears the mark_id attribute If you target a character that doesnt have a mark: it checks the mark_id attribute, and if that has an id, it removes the mark from that character It then adds the mark to to the targetted character and adds their id to the mark_id attribute There's no reason you couldnt have the global damage flag updated by this as well, but I've left it untouched as requested. I think this does what you need. Let me know if it doesnt work as intended. One concern I have is that it is hardcoded to Vesper. What happens if another player gets the Hunter's Mark ability? it is posisble to update the script to handle such cases, but it is extra work, so I left it in case it's never going to be needed. /* !huntersmark @{target|token_id} */ on('ready', () => { const script_name = 'Hunters Mark'; const marker_name = 'Marked::2079759'; const hunter_name = 'Vesper'; const mark_attr_name = 'mark_id'; const removeMarker = (tid, marker) => { const token = getObj('graphic', tid); let tokenMarkers = token.get('statusmarkers').split(','); tokenMarkers = tokenMarkers.filter(item => item !== marker); token.set('statusmarkers', tokenMarkers.join(',')); }; const addMarker = (tid, marker) => { const token = getObj('graphic', tid); let tokenMarkers = token.get('statusmarkers').split(','); tokenMarkers.push(marker); token.set('statusmarkers', tokenMarkers.join(',')); }; on('chat:message', msg => { const args = msg.content.split(/\s/); if(args[0].toLowerCase() === '!huntersmark') { /* setting up the function and variables */ const target_id = args[1]; const hunter = findObjs({ type: 'character', name: hunter_name })[0]; if(typeof hunter === 'undefined') { sendChat(script_name, "/w GM Vesper tried to run Hunters Mark but his character wasn't found."); return; } if(typeof target_id === 'undefined') { sendChat(script_name, '/w "' + hunter_name + '" Target is invalid.'); return; } log(hunter.get('id')); let mark_attribute = findObjs({ type: 'attribute', characterid: hunter.get('id'), name: mark_attr_name })[0]; if(typeof mark_attribute == 'undefined') { // create the attribute if it doesnt exist // this is only here so i could test the macro script myself. But if mark_id gets accidentally deleted this will restore it. mark_attribute = createObj("attribute", { name: mark_attr_name, current: '', max: '', characterid: hunter.get('id') }); } /* here starts the actual work of the script */ if(target_id == mark_attribute.get('current')) { // the target token matches the id stored in owner. // This character is already marked, so unmark him and clear mark_id mark_attribute.set('current', ''); removeMarker(target_id, marker_name); } else { // marking a new target so: // get old mark_id to remove mark from previous character // update mark_id and add marker const oldmark = mark_attribute.get('current'); if(oldmark !== '') { removeMarker(oldmark, marker_name); } mark_attribute.set('current', target_id); addMarker(target_id, marker_name); } } }); });
1593323333
Hey! Wow, thanks a ton for your help!  Unfortunately, I am getting this error:  It's definitely from this script, since I disabled it and didn't get the error, then re-enabled it and got the same error. I did a find all for ( and couldn't find any that were missing their ), as far as I can tell. But again—I know nothing :P Also, is this ' situation a problem? Line 32:   From the almost nothing I know about Javascript, I think the yellow text is one type of thing (variable?) and the white text is another (plain? vanilla?) and the wasn't  is cutting things in half? -- Without taking up your time, any way you could make this more generally-usable by others would obviously be helpful! Such as the stuff you mentioned about not hard-coding in Vesper. I'm planning on running this as a character ability on the character Vesper, if that helps make things easier. And while I am using the CMaster feature to add the Marked tokenmarker, others might not. Of course that's all beyond the scope of what I'm requesting help for. So please, don't spend any time doing any of this that you don't want to do; I appreciate everything you're doing so far and am just spouting ideas that might take countless hours to actually code, for all I know (as non-code-proficient people are apt to do, I suppose).
1593326310

Edited 1593330909
GiGs
Pro
Sheet Author
API Scripter
Oops, yes, that's certainly the issue. That's what you get for making last minute text changes without checking them. That line should be sendChat(script_name, "/w GM Vesper tried to run Hunters Mark but his character wasn't found."); The problem there was the apostrophe in was being interpreted as the end of that string, and roll20 was trying to interpret what came after ( t found.) as a command. Which broke things.
1593401806
Whoops, I'm fairly certain I got this error while using the script: TypeError: Cannot read property 'get' of undefined TypeError: Cannot read property 'get' of undefined at removeMarker (apiscript.js:21973:34) at apiscript.js:22029:21 at eval (eval at <anonymous> (/home/node/d20-api-server/api.js:154:1), <anonymous>:65:16) at Object.publish (eval at <anonymous> (/home/node/d20-api-server/api.js:154:1), <anonymous>:70:8) at /home/node/d20-api-server/api.js:1648:12 at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:560 at hc (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:39:147) at Kd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:546) at Id.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:489) at Zd.Ld.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:94:425) It popped up right as I tried using the script on an ettin. The targeted token DOES have a character it's linked to. Please let me know how I can be of more help in troubleshooting this!
1593404678

Edited 1593412972
GiGs
Pro
Sheet Author
API Scripter
I might not have time to look at this before bed, but can you give as much information about how this error happened? Were you adding a marker to the ettin with another character already marked? Were you adding a marker to the ettin with no other character marked? Were you removing a marker from the ettin? If you already had a mark on another character and were marking the ettin, what was the other character? Also, had you used the script successfully on otehr characters, or did it fail the first time you tried to use it. Also, can you reproduce the error, and if so, how? Note that the fact the ettin was linked to a character shouldnt matter. For the tokens you are targetting, this only affects the token, it doesnt use their character at all.
1593438656
GiGs
Pro
Sheet Author
API Scripter
I havent had a chance to look at this yet, but I have expanded the script somewhat. In the new version, posted here:&nbsp; <a href="https://gist.github.com/G-G-G/a8350e1e1e688c43a787e5b58c71d7ae" rel="nofollow">https://gist.github.com/G-G-G/a8350e1e1e688c43a787e5b58c71d7ae</a> You can set any character up with a hunters mark-like ability, give tjem each different (or the same) status markers, and it stores the details in script state, so you dont need a mark_id attribute on the sheet. Here's a screenshot as a taster, though the style and options shown here still need a bit of styling work for consistency. Once its tested a bit more, I';ll make a separate thread for it.&nbsp; To set up a character as a Hunter (or anyone with an ability that requires you to mark another character), select your character, and activate the token marker you want to use as their marker. This is so the script can detect which one to use. It must be the only status marker on them at that time. Then run !huntersmark add or just use the menu item. Once you are a hunter you can mark or unmark others by using&nbsp; !huntersmark @{selected|character_id} @{target|token_id} or click the Mark Target menu item (which does the same thing). For your own macros, you can replace&nbsp; @{selected|character_id} &nbsp;with one hardcoded for your character name, like&nbsp; @{Vesper|character_id} , if you dont want to need your character selected. Most of the code added in this script is for the styling and enabling the new features. Whatever problem occurred with the Ettin hasnt been fixed. I need to know how that happened, and I don't yet. So the script might not be robust yet and needs some testing.
1593447169
Wow, incredible!&nbsp; Sorry for not getting back to you about the ettin quicker, was dog-tired last night. The ettin situation was, in addition to what was described above, as follows (per your questions): Were you adding a marker to the ettin with another character already marked? no other characters were marked; I was adding a marker to the ettin with a clean slate Were you adding a marker to the ettin with no other character marked? see above Were you removing a marker from the ettin? the ettin did not have a mark
1593447522
Just installed the new script. TypeError: Cannot read property 'get' of undefined TypeError: Cannot read property 'get' of undefined at removeMarker (apiscript.js:21973:34) at apiscript.js:22029:21 at eval (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:154:1), &lt;anonymous&gt;:65:16) at Object.publish (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:154:1), &lt;anonymous&gt;:70:8) at /home/node/d20-api-server/api.js:1648:12 at /home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:560 at hc (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:39:147) at Kd (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:546) at Id.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:93:489) at Zd.Ld.Mb (/home/node/d20-api-server/node_modules/firebase/lib/firebase-node.js:94:425) Placed "Marked" on character Vesper (no other marks on any tokens) Typed "!huntersmark add" - no discernable results (no chat popup, etc.) Typed "!huntersmark" with nothing selected to try to pull up help dialogue Skimmed through code to find help command, then typed "!huntersmark help" - no results Checked API output console and found that error
1593448101
Restarted API sandbox and tried again and it worked fine! My only problem with its functionality is that CMaster doesn't seem to "see" the Marked marker being added, and as such doesn't set he repeating_damagemod_$1_global_damage_active_flag to 1. I'm not sure if it's possible for it to be applied in a way that CMaster would "see" the application of Marked, or any other token marker for that matter. I don't want you to have to do more work than is necessary, since you're already volunteering your own time (which I super appreciate). Whatever would be easier for you (make it so CMaster "sees" markers being added; allow for global damage mods to be toggled based on presence of marker) I would appreciate, should you be so inclined to fill out the features of this script. Again, should that be onerous, I can get around it by just adding a !chatsetattr command in the macro to turn Vesper's global damage mod on. When the marker is removed later, CMaster does see that, and turns his global damage mod off. So, truly, do not do any more than what you are willing and desiring to do with this script—you've already done a great service to me!
1593448387
After getting my macro set up (with the !chatsetattr thing described above), I haven't encountered a problem. -- For posterity, this is what I've got appended to the end of the macro (put the spell text up front as normal): !huntersmark @{character_id} @{target|token_id} !setattr --name Vesper --silent --repeating_damagemod_$1_global_damage_active_flag|1 Note that this only works if stored as a character ability, not as a macro. And obviously replace "vesper" with your character's name and change $1 to whatever position the Hunter's Mark extra d6 is stored as in your repeating global damage mod section (0 is the first position) If you want to store this as a macro, rather than a character ability: !huntersmark @{Vesper|character_id} @{target|token_id} !setattr --name Vesper --silent --repeating_damagemod_$1_global_damage_active_flag|1
1593470193
GiGs
Pro
Sheet Author
API Scripter
Adam T. said: My only problem with its functionality is that CMaster doesn't seem to "see" the Marked marker being added, and as such doesn't set he repeating_damagemod_$1_global_damage_active_flag to 1.&nbsp; Ah yes, scripts don't trigger events like that (not without some rewriting of CMaster, probably). That's a security limitation of roll20. I'm wondering if I should include such functionality directly in this script: define an attribute, and the value it gets set to when a target is marked or unmarked. The purpose of this script isnt meant to replace tokenMod or conditionMaster, its just for a very specific situation that those dont cover-&nbsp; when one character marks others and needs to keep a 'memory' of which token is marked. !huntersmark @{character_id} @{target|token_id} !setattr --name Vesper --silent --repeating_damagemod_$1_global_damage_active_flag|1 Note that this only works if stored as a character ability, not as a macro.&nbsp; The reason the above only works as an ability is because it lacks character-identifying code. As a Macro, this @{character_id} would need to be @{Vesper|character_id} for example (or @{selected| to make it more universal). I havent been able to reproduce the ettin error, but I'll look over the code and see if I can find and plug some potential sources of errors. That initial code was written pretty quickly :) It looks like the second crash happened at the same place in the code, which helps. If you still have the original script installed, make sure that's deleted.
1593471852
The older script was deleted prior to any further tests. !huntersmark @{character_id} @{target|token_id} !setattr --name Vesper --silent --repeating_damagemod_$1_global_damage_active_flag|1 Note that this only works if stored as a character ability, not as a macro.&nbsp; The reason the above only works as an ability is because it lacks character-identifying code. As a Macro, this @{character_id} would need to be @{Vesper|character_id} for example (or @{selected| to make it more universal). Ah, yes, I'm aware, sorry—I was just posting that in case the other people who have viewed the thread may want to use the same sort of thing I'm asking your help for. I may not be knowledgeable in building scripts, but I'm fairly confident in my knowledge of macros and that side of things! :) -- As for the following: I'm wondering if I should include such functionality directly in this script: define an attribute, and the value it gets set to when a target is marked or unmarked. I wouldn't be opposed to it—that would make the script more standalone. As it is now, it does its purpose perfectly, which is to add a marker to one token whilst clearing it from the token that previously held said marker. I suppose that is perfectly functional enough for many, and those of us like myself crazy enough to want to automate everything as much as possible can just build said macro to contain !chatsetattr like I have above. It's your decision! My hat is already off to you, good sir. Thanks so much for this. I'll keep popping back in if I encounter any bugs and to check if you have updates or other thoughts/comments/etc.
1593472016
GiGs
Pro
Sheet Author
API Scripter
You're welcome, and please do report any bugs/thoughts that crop up :)
1593489315
GiGs
Pro
Sheet Author
API Scripter
I just made a tweak to the script, fixing one possible (but unlikely) source of crashes, and some minor style changes. You can replace the original script without losing your stored hunter data:&nbsp; <a href="https://gist.github.com/G-G-G/a8350e1e1e688c43a787e5b58c71d7ae" rel="nofollow">https://gist.github.com/G-G-G/a8350e1e1e688c43a787e5b58c71d7ae</a>