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

Concentration Script - Character Selected ERROR

Hello, I'm not well versed in API scripts, but I've managed to work out some scripts and macros previously.    I'm workshopping a script based on   these two   threads   to create a Concentration script for the 5e OGL sheet that automatically rolls a constitution saving throw with the appropriate DC listed when you subtract HP from a token.  Currently, when subtracting HP from the token, this error appears: "ERROR: Unable to find character selected in chat command."  I've double checked that my tokens are attached to character sheets, and I've tried replacing "@" with the html codes or with"\@" and "\\@" as suggested in other threads. I'm not sure what my issue is or how to resolve it. Any input is appreciated - thanks in advance! Here is the script I have right now: var TOKEN_CONCENTRATING_STATUS_MARKER = "status_" + "overdrive"; on("change:graphic:bar1_value", function(obj, prev) { if (obj.get(TOKEN_CONCENTRATING_STATUS_MARKER)) { var playerPage = Campaign().get("playerpageid"); var tokenPage = obj.get("_pageid"); if (prev["bar1_value"] > obj.get("bar1_value")) { var final_conc_DC = 10; var calc_conc_DC = (prev["bar1_value"] - obj.get("bar1_value")) / 2; if (calc_conc_DC > final_conc_DC) { final_conc_DC = Math.floor(calc_conc_DC); } sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{r1={@selected|d20}+@{selected|constitution_save}}} {{saving_throw_dc=" +final_conc_DC + "}}"); } } });
1757624482

Edited 1757624649
Pat
Pro
API Scripter
I think when you're in the script, the notation for @selected doesn't work... you want to use the obj that you've been handed which would be the token from the change event. So the token reference is probably what you'd be using instead...  That is to say, typically when you're inside a script instead of firing from a macro especially one that is dispatched from a change event, you'd need to provide the token reference in a way that the macro would be able to interpret and understand - so instead of @target or @selected, you'd use the "named" variant and query the token object for its reference to create the macro to dispatch to chat. 
Apologies, I'm not very fluent with all the relevant terms. Based on quick google searches, I'm interpreting this to mean that I would have to run a separate script for each token I want to apply it to - is that correct? Or is there a way to recall an object/token's reference by having it selected? I'm not sure I understand. 
1757629108
Pat
Pro
API Scripter
Noah said: Apologies, I'm not very fluent with all the relevant terms. Based on quick google searches, I'm interpreting this to mean that I would have to run a separate script for each token I want to apply it to - is that correct? Or is there a way to recall an object/token's reference by having it selected? I'm not sure I understand.  Well, I'll try and break down what's happening.  Normally, when you plug in a chat element things run like this:  You --> [Chat Engine] --> Chat pulls contexts for your command like filling in @target and @selected based on YOU the person who entered something into chat.  The "context" here is your player (DM, player, etc) and it sees what *you* selected when you put in @selected.  When an event dispatch happens things run like this:  Event --> Back end Engine --> Evaluation --> Chat Dispatch.  Note that nowhere in there is it referencing "You"  So when you try and run that Chat Dispatch:  You --> [Chat Engine] --> Gets your selected object Back end Engine --> [Chat Engine] --> Has no idea who it should look to for a selected token. 
1757644934

Edited 1757645027
timmaugh
Forum Champion
API Scripter
I agree with Pat. Let me see if I can put it another way to help clarify what's going on. First, your code has a typo in that you reversed the @ and the left brace in one of your @{selected} references. But what is really going to be the show stopper is that your script is issuing a command which WILL NOT have selected tokens. (I suggest watching the first 15 minutes of this video I made explaining scripts to understand why this message can NEVER have selected tokens. You can watch beyond that point, but it gets into metascripts, which are a whole other level of weirdness that you don't need to deal with... yet.) =D Since the message sent by your script can never have tokens, the @{selected} reference will never resolve when you send the message through the chat parser. It might try to find a character named "selected," but that won't resolve either. So instead of using the @{selected} formation, you would need to use the NAME version (as Pat mentioned): @{Actual Cannibal|d20} This doesn't mean you have to have a different script for each character; it means you have to derive the character's name dynamically from the information you DO have. Here's how you can do that: Any selected token is going to be on the message object in the  .selected  property array, so a good first step is to make sure the property is there and has at least one reference in it. if(!(msg.selected && msg.selected.length)) {    // maybe provide a notification that a selected token is required for this command  return; }  Once we know it's there, you will need to get the reference from the array; I'm going to assume you're looking for the first entry in the array (entry 0): let sel = msg.selected[0]; Then you will need to use it to actually get the token: let token = getObj('graphic', sel._id); Note the underscore as a part of the property name. This is necessary when you're looking at the selected property. Now that you have the token, you have to see if it represents a character: if(!token.get('represents').length) {   // again, maybe a notification of this token not representing a character   return; } If it represents a character, you can use that value to drive another retrieval, this time of the character object. We'll need that in order to get the character name. (Yes, most times a character-representing token will have the same name as the character it represents and you *could* use the token name... but there are times that the name has changed, especially for mooks, duplication, etc.) let char = getObj('character', token.get('represents')); Then you can get the name from the character so that you can use it, later: let charName = char.get('name'); Now you can build your sendChat command using this name instead of "selected": sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save}}} {{saving_throw_dc=" + final_conc_DC + "}}"); When your script sends that, the command line that hits the chat is something more like: &{template:simple} {{name=Concentration Save}} {{r1=@{Actual Cannibal|d20}+@{Actual Cannibal|constitution_save}}} {{saving_throw_dc=12}}" ...and Roll20 should be able to find the character named "Actual Cannibal". The above is the more readable, "verbose" version of the code. There are a lot of efficiencies that could be applied to it to reduce the number of lines you have to add to your script, but when it's written this way, you can see how each part builds on the previous and, hopefully, why we have to go this direction to get the result you're looking for.
Thank you, I think I'm understanding things a bit better. However, I'm now getting an error stating that "msg is not defined". Is this because I'm not using a chat command, but rather changing a bar value?  Here is the script I have now. var TOKEN_CONCENTRATING_STATUS_MARKER = "status_" + "overdrive"; on("change:graphic:bar1_value", function(obj, prev) { if (obj.get(TOKEN_CONCENTRATING_STATUS_MARKER)) { var playerPage = Campaign().get("playerpageid"); var tokenPage = obj.get("_pageid"); if (prev["bar1_value"] > obj.get("bar1_value")) { var final_conc_DC = 10; var calc_conc_DC = (prev["bar1_value"] - obj.get("bar1_value")) / 2; if (calc_conc_DC > final_conc_DC) { final_conc_DC = Math.floor(calc_conc_DC); } if(!(msg.selected && msg.selected.length)) { // maybe provide a notification that a selected token is required for this command return; } let sel = msg.selected[0]; let token = getObj('graphic', sel._id); if(!token.get('represents').length) { // again, maybe a notification of this token not representing a character return; } let char = getObj('character', token.get('represents')); let charName = char.get('name'); sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save}}} {{saving_throw_dc=" + final_conc_DC + "}}"); } } });
1757688248

Edited 1757697630
timmaugh
Forum Champion
API Scripter
oh, whoops. I saw the @{selected} in your sendChat and assumed you were in a "chat:message" event, not a "change:graphic:bar1_value" event. You don't have a message object; you have the token as a Roll20 object, and the previous state of the token as a standard javascript object. That makes it a bit simpler. Since you already have the token as your "obj" parameter to this function, you don't need these 2 lines: let sel = msg.selected[0]; let token = getObj('graphic', sel._id); And, after that, change the reference to  token  to be  obj  (which is how the token is being passed to your function). So that means the subsequent IF statement will read: if(!obj.get('represents').length) { ...and the definition of your char variable will look like: let char = getObj('character', obj.get('represents')); That should sort you out.
1757689847

Edited 1757690146
Noah
Pro
That works! Thank you! The only thing I don't have working as intended is the product isn't showing the save DC or the name of the roll.  This is the script: var TOKEN_CONCENTRATING_STATUS_MARKER = "status_" + "overdrive"; on("change:graphic:bar1_value", function(obj, prev) { if (obj.get(TOKEN_CONCENTRATING_STATUS_MARKER)) { var playerPage = Campaign().get("playerpageid"); var tokenPage = obj.get("_pageid"); if (prev["bar1_value"] > obj.get("bar1_value")) { var final_conc_DC = 10; var calc_conc_DC = (prev["bar1_value"] - obj.get("bar1_value")) / 2; if (calc_conc_DC > final_conc_DC) { final_conc_DC = Math.floor(calc_conc_DC); } if(!obj.get('represents').length) { // again, maybe a notification of this token not representing a character return; } let char = getObj('character', obj.get('represents')); let charName = char.get('name'); sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save_roll}}} {{saving_throw_dc=" + final_conc_DC + "}}"); } } }); And this is the product when subtracting hp from the token: Have I incorrectly formatted the script to display the save DC and name of the roll?
1757690413

Edited 1757690765
Noah said: That works! Thank you! The only thing I don't have working as intended is the product isn't showing the save DC.  This is the script: sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save_roll}}} {{saving_throw_dc=" + final_conc_DC + "}}"); And this is the product when subtracting hp from the token: Have I incorrectly formatted the script to display the save DC? In the original post, KeithCurtis' macro was using the 5e Shaped Sheet, which has different roll template fields. The D&D 5E 2014 by Roll20 sheet ' 'Simple' template does not have a field for 'saving_throw_dc'. These are the fields that it uses: &{template:simple} {{rname=rname}} {{mod=mod}} {{r1=r1}} {{always=1}} {{r2=r2}} {{charname=charname} I would suggest using the 'mod' field: sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save_roll}}} {{ mod="**DC + final_conc_DC + ** "}}"); Edit: forgot to add the part about the name of the roll: I would suggest using the 'rname' field for the roll: sendChat("TokenDamage", "&{template:simple} {{name=Concentration Save}} {{rname=Concentration}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save_roll}}} {{ mod="** DC+ final_conc_DC + ** "}}"); I just looked again and I see that you're already using the 'name' field for "Concentration Save", but it's not appearing in the output. Weird. It looks like somehow the 'mod' field and 'name' field are being populated already, or overwritten.
1757691151

Edited 1757691213
Noah
Pro
I realized I needed to make the name section "rname" not just "name". So now the script works how I hoped! var TOKEN_CONCENTRATING_STATUS_MARKER = "status_" + "overdrive"; on("change:graphic:bar1_value", function(obj, prev) { if (obj.get(TOKEN_CONCENTRATING_STATUS_MARKER)) { var playerPage = Campaign().get("playerpageid"); var tokenPage = obj.get("_pageid"); if (prev["bar1_value"] > obj.get("bar1_value")) { var final_conc_DC = 10; var calc_conc_DC = (prev["bar1_value"] - obj.get("bar1_value")) / 2; if (calc_conc_DC > final_conc_DC) { final_conc_DC = Math.floor(calc_conc_DC); } if(!obj.get('represents').length) { // again, maybe a notification of this token not representing a character return; } let char = getObj('character', obj.get('represents')); let charName = char.get('name'); sendChat("TokenDamage", "&{template:simple} {{rname=Concentration Save DC}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save_roll}}} {{mod= "+ final_conc_DC +"}}"); } } }); And this is how it looks: Thank you all for your help! I'm slowly, slowly learning!
I went back and actually put the DC in the rname so it still displays your modifier. var TOKEN_CONCENTRATING_STATUS_MARKER = "status_" + "overdrive"; on("change:graphic:bar1_value", function(obj, prev) { if (obj.get(TOKEN_CONCENTRATING_STATUS_MARKER)) { var playerPage = Campaign().get("playerpageid"); var tokenPage = obj.get("_pageid"); if (prev["bar1_value"] > obj.get("bar1_value")) { var final_conc_DC = 10; var calc_conc_DC = (prev["bar1_value"] - obj.get("bar1_value")) / 2; if (calc_conc_DC > final_conc_DC) { final_conc_DC = Math.floor(calc_conc_DC); } if(!obj.get('represents').length) { // again, maybe a notification of this token not representing a character return; } let char = getObj('character', obj.get('represents')); let charName = char.get('name'); sendChat("TokenDamage", "&{template:simple} {{rname=Concentration Save DC "+ final_conc_DC +"}} {{r1=@{" + charName + "|d20}+@{" + charName + "|constitution_save_roll}}}"); } } });