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

Distance from Selected token to Target?

1626295841

Edited 1626297353
Only scripts I could find were using Target 1 to Target 2. Any way to use Selected token to Target token? With a way to convert the distance to ranged attack penalty and include it in the roll. 3 hexes would be -1, 4 to 5 hexes would be -2, 6-7 hexes -3, 8-10 hexes -4. etc.
I have been using a roll query where you insert the range in the query and the penalty is calculated from that. Sadly I haven't found an API that calculates the distance between selected and target token and the applies that in the macro. In my work around you still have to calculate the distance on your own.
1626305289
timmaugh
Pro
API Scripter
This is on my to-do list of meta scripts... That way, you could use the result in other scripts' command lines/macros.
1626351824
The Aaron
Roll20 Production Team
API Scripter
There's some distance scripts there that might work:&nbsp; <a href="https://app.roll20.net/forum/post/6110806/cant-get-distance-api-to-work" rel="nofollow">https://app.roll20.net/forum/post/6110806/cant-get-distance-api-to-work</a>
The Aaron said: There's some distance scripts there that might work:&nbsp; <a href="https://app.roll20.net/forum/post/6110806/cant-get-distance-api-to-work" rel="nofollow">https://app.roll20.net/forum/post/6110806/cant-get-distance-api-to-work</a> Alas, that one still uses two targets. Also, how would incorporate the scipt output in a calculation? Such as [[100 -Distance +3d6]] ?
1626372678
The Aaron
Roll20 Production Team
API Scripter
It would be easier to put the calculation in the script. I'll try to take a look when I'm on my computer.&nbsp;
Alternatively, the final value could be stored in a character atribute, and then that would be used by the calculation.
Scriptcards has a number of distance functions.
One of the issues I had with the measurement APIs (radar as well) is the results come back in their own template layout so they can't really be inserted in other templates/powercards. If they could generate results in a generic inline version then maybe the distance can be generated as an inline roll? But my API fu is level 0 so .....magic.&nbsp;
1626526654
timmaugh
Pro
API Scripter
Peacekeeper B said: One of the issues I had with the measurement APIs (radar as well) is the results come back in their own template layout so they can't really be inserted in other templates/powercards. If they could generate results in a generic inline version then maybe the distance can be generated as an inline roll? But my API fu is level 0 so .....magic.&nbsp; That's exactly what a meta-script would do. =D
Cool. I was thinking just adjust the script in question. I would love to be able to launch !spawn or !radar from the same powercard that initiates the spell instead of through a button&nbsp;
Ok, so here is the script Im working with: // Computes the distances between 2 tokens by ID // Use the macro target picker to fill in the ID // Script command // !range @{target|1st Target|token_id} @{target|2nd Target|token_id} on("chat:message", function(msg) { &nbsp;&nbsp;&nbsp; if(msg.type == "api" &amp;&amp; msg.content.indexOf("!range") != -1) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log(msg);&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var n = msg.content.split(" ", 4) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var cmd = n[0]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var id1 = n[1]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var id2 = n[2]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var token1 =&nbsp; getObj("graphic", id1); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var token2 =&nbsp; getObj("graphic", id2); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (token1 &amp;&amp; token2) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var distance = tokenDistance(token1, token2); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* sendChat("", "/desc Distance between Token1 &amp; Token2 is " + distance); */ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sendChat("", "/desc Range modifier is " +rangeMod(distance)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rangeMod(distance) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!token1) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sendChat("Range Finder","/w " + msg.who + " Could not locate first token!"); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!token2) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sendChat("Range Finder","/w " + msg.who + " Could not locate second token!"); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp;&nbsp;&nbsp; } }); function tokenDistance(token1, token2) { &nbsp;&nbsp;&nbsp; if (token1.get('pageid') != token2.get('pageid')) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log('Cannot measure distance between tokens on different pages'); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return; &nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp; var distance; &nbsp;&nbsp;&nbsp; var page = getObj('page', token1.get('pageid')); &nbsp;&nbsp;&nbsp; var gridType = page.get('grid_type'); &nbsp;&nbsp;&nbsp; switch(gridType) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 'hex': &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; distance = hexVDistance([token1.get("left"), token1.get("top")], [token2.get("left"), token2.get("top")]); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 'hexr': &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; distance = hexHDistance([token1.get("left"), token1.get("top")], [token2.get("left"), token2.get("top")]); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp; return distance; } function hexHDistance(unit1, unit2) { &nbsp;&nbsp;&nbsp; var q1, q2, r1, r2; &nbsp;&nbsp;&nbsp; q1 = Math.round((unit1[0] - 46.48512749037782) / 69.58512749037783); &nbsp;&nbsp;&nbsp; r1 = Math.round((unit1[1] - 39.8443949917523) / 39.8443949917523); &nbsp;&nbsp;&nbsp; r1 = Math.floor(r1 / 2); &nbsp;&nbsp;&nbsp; q2 = Math.round((unit2[0] - 46.48512749037782) / 69.58512749037783); &nbsp;&nbsp;&nbsp; r2 = Math.round((unit2[1] - 39.8443949917523) / 39.8443949917523); &nbsp;&nbsp;&nbsp; r2 = Math.floor(r2 / 2); &nbsp;&nbsp;&nbsp; return cubeDistance(oddQToCube(q1, r1), oddQToCube(q2, r2)); } function hexVDistance(unit1, unit2) { &nbsp;&nbsp;&nbsp; var q1, q2, r1, r2; &nbsp;&nbsp;&nbsp; q1 = Math.round((unit1[0] - 37.59928099223013) / 37.59928099223013); &nbsp;&nbsp;&nbsp; r1 = Math.round((unit1[1] - 43.86582782426834) / 66.96582782426833); &nbsp;&nbsp;&nbsp; q1 = Math.floor(q1 / 2); &nbsp;&nbsp;&nbsp; q2 = Math.round((unit2[0] - 37.59928099223013) / 37.59928099223013); &nbsp;&nbsp;&nbsp; r2 = Math.round((unit2[1] - 43.86582782426834) / 66.96582782426833); &nbsp;&nbsp;&nbsp; q2 = Math.floor(q2 / 2); &nbsp;&nbsp;&nbsp; return cubeDistance(oddRToCube(q1, r1), oddRToCube(q2, r2)); } function oddRToCube(q, r) { &nbsp;&nbsp;&nbsp; var x, y, z; &nbsp;&nbsp;&nbsp; x = q - (r - (r &amp; 1)) / 2; &nbsp;&nbsp;&nbsp; z = r; &nbsp;&nbsp;&nbsp; y = -x - z; &nbsp;&nbsp;&nbsp; return [x, y, z]; } function oddQToCube(q, r) { &nbsp;&nbsp;&nbsp; var x, y, z; &nbsp;&nbsp;&nbsp; x = q; &nbsp;&nbsp;&nbsp; z = r - (q - (q &amp; 1)) / 2; &nbsp;&nbsp;&nbsp; y = -x - z; &nbsp;&nbsp;&nbsp; return [x, y, z]; } function cubeDistance(cube1, cube2) { &nbsp;&nbsp;&nbsp; return Math.max(Math.abs(cube1[0] - cube2[0]), Math.abs(cube1[1] - cube2[1]), Math.abs(cube1[2] - cube2[2])); } function rangeMod(distR) { &nbsp;&nbsp;&nbsp; switch(true) { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case (distR &lt; 3): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case (distR &lt; 4): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case (distR &lt; 6): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -2; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case (distR &lt; 8): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -3; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case (distR &lt; 11): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -4; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case (distR &lt; 111): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -5; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;&nbsp;&nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log('not a coded range'); &nbsp;&nbsp;&nbsp; } } So is there any way to change the var token1 =&nbsp; getObj("graphic", id1); to selected toke ID?
1626715868
timmaugh
Pro
API Scripter
You don't want to change that one, I don't think. When the line is parsed and split on spaces, you fill your ids. If you don't supply 2 targeted token ids, not all of the ids will be filled... and it will be the *2nd* id that will be blank. So, even as it stands, it looks like you could do: !range @{target|token_id} @{selected|token_id} To have it read the selected token's id as one of the parameters. If you wanted that part to be optional, you can also change where the 2nd id is being assigned: var id2 = n[2] || msg.selected[0]._id || undefined; That way, if you don't include the second target, it will still work by trying to look for a selected token... if *that* isn't found, it will be undefined (which, after all, is the fall back, anyway).
Thanks! That works great. Now, instead of chat output, the penalty should be included in the inline roll... Perhaps the script output could be saved in a Mule atribute and then immediately retrieved? Can this be done within the same macro?
1626727743

Edited 1626813245
timmaugh
Pro
API Scripter
Kaspar K. said: Now, instead of chat output, the penalty should be included in the inline roll... Perhaps the script output could be saved in a Mule atribute and then immediately retrieved? Can this be done within the same macro? Yes, generally things can be written and retrieved from a Mule within the same macro... however in this case the output of the range script was different than the value you wanted to store, so it would have taken changing the output text of the range script to include the Muler verbiage. Instead, I took the above script and rejiggered it to be a Plugger plugin. It should still run from the command line if you wanted to send it through on its own: !range @{target|token_id} @{selected|token_id} !range @{target|Target 1|token_id} @{target|Target 2|token_id} !range @{target|Get Distance From Selected to this Token|token_id} But now you can also return the value of the range modifier to the command line by embedding it in a Plugger EVAL structure: {&amp; eval}range(@{target|token_id}){&amp; /eval} To have that work in an inline roll, you have to delay the roll until the range modifier is returned. That would look like this...&nbsp; [\][\] ... \]\] So plug your roll math into the middle. For a roll of "1d20 minus the range mod" that would be: [\][\]1d20 -&nbsp;{&amp; eval}range(@{target|token_id}){&amp; /eval}\]\] Deferring a roll like this requires ZeroFrame. Keeping a selected token through all of this might require SelectManager. And obviously, this will require Plugger to run the range script as a plug-in. Here is the re-written script: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 // Range script rewrite // script will work as standalone or as Plugger plugin (to return the range mod to the command line) const Range = (() =&gt; { // !getRange @{target|1st Target|token_id} @{target|2nd Target|token_id} const handleInput = msg =&gt; { log(msg.content); if (msg.type !== "api" || ! /^!range\s+.+/i .test(msg.content)) return ; let tokens = msg.content.split( /\s+/ , 3 ).slice( 1 ); let id1 = tokens[ 0 ]; let id2 = tokens[ 1 ] || ((msg.selected || [])[ 0 ] || {})._id || undefined ; log( ` ID1: ${id1} ` ); log( ` ID2: ${id2} ` ); if (!(id1 &amp;&amp; id2)) return ; let token1 = getObj( "graphic" , id1); let token2 = getObj( "graphic" , id2); let distance = 0 ; let rMod; if (token1 &amp;&amp; token2 &amp;&amp; token1.get( 'pageid' ) === token2.get( 'pageid' )) { distance = tokenDistance(token1, token2); rMod = rangeMod(distance); if (msg. eval ) { return rMod; } else { sendChat( "" , "/desc Range modifier is " + rMod); } } else { distance = undefined ; if (token1 &amp;&amp; token2 &amp;&amp; token1.get( 'pageid' ) !== token2.get( 'pageid' )) { sendChat( "Range Finder" , "/w " + msg.who + " Tokens are not on the same page!" ); } if (!token1) sendChat( "Range Finder" , "/w " + msg.who + " Could not locate first token!" ); if (!token2) sendChat( "Range Finder" , "/w " + msg.who + " Could not locate second token!" ); } }; const tokenDistance = (token1, token2) =&gt; { if (token1.get( 'pageid' ) !== token2.get( 'pageid' )) { log( 'Cannot measure distance between tokens on different pages' ); return ; } let distance; let page = getObj( 'page' , token1.get( 'pageid' )); let gridType = page.get( 'grid_type' ); switch (gridType) { case 'hex' : distance = hexVDistance([token1.get( "left" ), token1.get( "top" )], [token2.get( "left" ), token2.get( "top" )]); break ; case 'hexr' : distance = hexHDistance([token1.get( "left" ), token1.get( "top" )], [token2.get( "left" ), token2.get( "top" )]); break ; } return distance; }; const hexHDistance = (unit1, unit2) =&gt; { let q1, q2, r1, r2; q1 = Math .round((unit1[ 0 ] - 46.48512749037782 ) / 69.58512749037783 ); r1 = Math .round((unit1[ 1 ] - 39.8443949917523 ) / 39.8443949917523 ); r1 = Math .floor(r1 / 2 ); q2 = Math .round((unit2[ 0 ] - 46.48512749037782 ) / 69.58512749037783 ); r2 = Math .round((unit2[ 1 ] - 39.8443949917523 ) / 39.8443949917523 ); r2 = Math .floor(r2 / 2 ); return cubeDistance(oddQToCube(q1, r1), oddQToCube(q2, r2)); }; const hexVDistance = (unit1, unit2) =&gt; { let q1, q2, r1, r2; q1 = Math .round((unit1[ 0 ] - 37.59928099223013 ) / 37.59928099223013 ); r1 = Math .round((unit1[ 1 ] - 43.86582782426834 ) / 66.96582782426833 ); q1 = Math .floor(q1 / 2 ); q2 = Math .round((unit2[ 0 ] - 37.59928099223013 ) / 37.59928099223013 ); r2 = Math .round((unit2[ 1 ] - 43.86582782426834 ) / 66.96582782426833 ); q2 = Math .floor(q2 / 2 ); return cubeDistance(oddRToCube(q1, r1), oddRToCube(q2, r2)); }; const oddRToCube = (q, r) =&gt; { let x, y, z; x = q - (r - (r &amp; 1 )) / 2 ; z = r; y = -x - z; return [x, y, z]; }; const oddQToCube = (q, r) =&gt; { let x, y, z; x = q; z = r - (q - (q &amp; 1 )) / 2 ; y = -x - z; return [x, y, z]; }; const cubeDistance = (cube1, cube2) =&gt; { return Math .max( Math .abs(cube1[ 0 ] - cube2[ 0 ]), Math .abs(cube1[ 1 ] - cube2[ 1 ]), Math .abs(cube1[ 2 ] - cube2[ 2 ])); }; const rangeMod = (distR) =&gt; { switch ( true ) { case (distR &lt; 3 ): return 0 ; case (distR &lt; 4 ): return - 1 ; case (distR &lt; 6 ): return - 2 ; case (distR &lt; 8 ): return - 3 ; case (distR &lt; 11 ): return - 4 ; case (distR &lt; 111 ): return - 5 ; default : log( 'not a coded range' ); } }; const registerEventHandlers = () =&gt; { on( "chat:message" , handleInput); }; const range = (m) =&gt; handleInput(m); on( "ready" , () =&gt; { registerEventHandlers(); try { Plugger.RegisterRule(range); } catch (error) { log(error); } }); })(); EDIT: Corrected 7/20/2021 for error mentioned below.
timmaugh said: Ok, so I updated the script, and added libInline, ZeroFrame and Plugger. How would the full text of the macro look like now?
Yeah. I'm lost. Lol.
1626735975
timmaugh
Pro
API Scripter
Hee hee! Ok, it might take you posting the formula for the roll you want to use and/or the macro you want to use it in. Basically, the EVAL block (I'll post it here again) is what you need to get the info you're looking for: {&amp; eval}range(@{target|token_id}){&amp; /eval} I posted the other part about using it in an inline roll since you said that that was going to be a requirement, but if that isn't enough info to get you across the finish line, then give me the rest of what you are wanting to do. Do you want to drop that in a roll template? Do you want to drop that in the command line to another script? Both of those can be done, I just have to understand what we're working with to begin with.
@{selected|token_name} Ranged attacks @{target|token_name} with [[@{selected|Bow}-3d6+ RangePenalty ]]
1626743262
timmaugh
Pro
API Scripter
Alright. Alright, alright, alright. !@{selected|token_name} ranged attacks @{target|token_name} with [\][\]@{selected|Bow}-3d6+{&amp; eval}range(@{target|token_id}){&amp; /eval}\]\]{&amp; simple} If it helps to see your original macro separate from the meta constructions, I thought about this way to show it to you, too: ! \] \] {&amp; eval}range(@{target|token_id}){&amp; /eval}\ \ {&amp; simple} @{selected|token_name} ranged attacks @{target|token_name} with [ [ @{selected|Bow}-3d6+ ] ] Top line is what it takes to run it as a meta construction. Bottom line is your original (except that your original couldn't deliver the range mod). A small explanation: It helps to think of things in terms of a loop of: Roll20 parsing =&gt; Meta parsing =&gt; ...repeat... At the end of one pass of that loop, we unescape the line (removing backslashes -- and \] if it's the last in the series of backslashed-inline-rolls). For our line, we can't let the Roll20 parsers roll the inline roll (before the metascripts get involved), because we need Plugger to return the value from the range script. That happens in the first pass of the loop. Once the value is returned, that trip through the loop ends and the backslashes are removed (one level). That leaves a recognizable inline roll behind. We loop back to Roll20 parsing, which now processes the inline roll. That's the end of our work (no more meta constructions are found), so the metascripts release the message. As a part of releasing the message, the message gets converted to a standard message because we included a {&amp; simple} tag.
1626798176

Edited 1626798950
timmaugh said: !@{selected|token_name} ranged attacks @{target|token_name} with [\][\]@{selected|Bow}-3d6+{&amp; eval}range(@{target|token_id}){&amp; /eval}\]\]{&amp; simple} Using this gets me the select target prompt, but no output in chat. API Output Console Your scripts are currently disabled due to an error that was detected. Please make appropriate changes to your script's code and click the "Save Script" button. We will then attempt to start running the scripts again. More info... If this script was installed from the Script Library, you might find help in the Community API Forum. For reference, the error message generated was: TypeError: Cannot read property 'config' of undefined TypeError: Cannot read property 'config' of undefined at initState (apiscript.js:6905:40) at handleInput (apiscript.js:7344:79) at eval (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:161:1), &lt;anonymous&gt;:65:16) at Object.publish (eval at &lt;anonymous&gt; (/home/node/d20-api-server/api.js:161:1), &lt;anonymous&gt;:70:8) at /home/node/d20-api-server/api.js:1721: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)
1626812877

Edited 1626813268
timmaugh
Pro
API Scripter
OK, sorry about that. Coding too quickly and I found a bug along the way. Here are a few notes... 1) ERROR Regarding the error you got, I think that was because I'd used a code template I had from another script, and didn't remove a line that we didn't need. In my environment, it worked because I had the function it was referencing. I believe that I have fixed the code, and will correct the post , above, to reflect it. 2) USAGE I should have noticed straight away that you were trying to mix targeting tokens and selected tokens... that will not work because there are no selected tokens in the message when you use a targeting statement. That said, you don't *have* to use the targeting statement to render your first ID; you could use a Fetch construction using someone's name: @(Volcano Man.token_id) !range @(Volcano Man.token_id) or, as a plugin {&amp; eval}range(@(Volcano Man.token_id){&amp; eval} (see the note, below, about order when you mix Plugger and Fetch) Because you can do that, I'll leave the code the way it is, where you only have to supply one ID and it will try to supply the second from the selected token, if it is available. If you do end up using a targeting statement, then you should explicitly supply the selected ID to the command line: !range @{target|token_id} @{selected|token_id} 3) ORDERING For what you intended to do, this won't matter. However, if you mix Plugger and Fetch, as I did, just above, you will want to order them explicitly. As they ship from the 1-click, they both come with the exact same priority to the ZeroFrame loop. (Run !0 in your chat and you'll see what I mean.) That means that ZeroFrame will run them in the order you installed them. In the above example, you have to have Fetch get the ID before you send the value to Plugger, so you have to make sure they are ordered that way. You can do that for this one call by including the following somewhere in your macro (just throw it in at the end): {&amp;0 fetch eval} Or you can run a command like this to set the default order for all calls: !0 fetch|45 eval|50 4) BUG I found a separate bug while troubleshooting this, and had to release new versions of each of the metascripts (APILogic, Fetch, MathOps, Muler, Plugger, and SelectManager). ZeroFrame was unaffected by the bug. These should all get updated with tomorrow's merge to the 1-click, but if you need them before that, you can get them at my personal repo . Get the new versions of the scripts (including the range script, above) and then give it another try. It should work for you.
Ok, how should the full macro look now?
1626869646
timmaugh
Pro
API Scripter
!@{selected|token_name} ranged attacks @{target|token_name} with [\][\]@{selected|Bow}-3d6+{&amp; eval}range(@{target|token_id} @{selected|token_id}){&amp; /eval}\]\]{&amp; simple}
Nice. Thank you very much.
1626883108
timmaugh
Pro
API Scripter
Great! I wanted to mention, too, that this is a direct port of the script you were originally using to be a Plugger plug-in. When I release an actual "range" metascript, it will probably be more fully featured (this one only works on hex, I believe), and it will be game-system-agnostic. That means it will be set up to return the actual distance between two tokens, not the modifier that would mean for your specific game. In that case, translating the distance into a modifier would probably require a mule table with entries like: &lt;3=0 &lt;4=-1 &lt;6=-2 &lt;8=-3 &lt;11=-4 &lt;111=-5 &gt;=111=-6 But we've only just gotten you up and working with this version, so I won't get into it right now. Point is, if/when I release that "official" range metascript, if you want to use it you will need to convert the above macro... but you can hit me up for that when the time comes, if that's the direction you want to go.