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 .
×
Advertisement Create a free account

Range Calculation

1631404753

Edited 1631405866
Is there a script out there to measure the number of hexes between two tokens (hex-h map) and return only the number of hexes?  I'd like to have it be a value in a range penalty calculation, but it looks like the !measure script (which is otherwise golden) prints text as part of the output. For reference/in case it matters, I'd like the calculation to be something like the below: floor((!measure @{selected|token_id} @{target|1|token_id})/40) e: I should specify that the above will be one of many modifiers affecting a shot within the shooting macro, and that the divisor (currently 40) will change depending upon which weapon is being fired.
1631413523
The Aaron
Forum Champion
API Scripter
Even writing a modified version of measure that only outputs the distance as a number, it wouldn't work as a math argument like that. You probably need a script that can output the whole block you're looking to display. 
1631425451
I was worried about that.  As a workaround, is there a way to store the output of the script to a character Attribute, where it could then be referenced?
1631442911

Edited 1631443032
If you don't mind inserting range yourself you can use the following macro. floor(?{Range to Target|0}/@{selected|WeaponRange})
1631449676
Yeah, that's my current placeholder, actually, but my goal is to minimize the number of things the player needs to click through in order to take a shot.  An automated solution would be the bee's knees.  Thanks, though!
Jeff H. said: Yeah, that's my current placeholder, actually, but my goal is to minimize the number of things the player needs to click through in order to take a shot.  An automated solution would be the bee's knees.  Thanks, though! I've been trying the same thing but haven't found a working version yet. Though I heard scriotcards can do it. Just don't ask me how. 
1631508163

Edited 1631512417
Sorry to ask, but I've been noodling around in !measure but haven't been able to figure out how to get it to output just the raw distance value without the associated text.  I think I know roughly where the edits need to go, but I just can't parse the script, and my hamhanded attempts to delete what I think are the extra bits and pieces just result in either partial output or no output;  I just can't hit the sweet spot.  Is it possible to get a code patch to drop in that would achieve this?  It may not work, but I'd like to try and see if I can kludge together a solution that will do what I (more or less) want it to do. Thanks! e: I think I managed to (sorta) get the output I was looking for, but, yeah, I think what I actually need in order to make this work the way I need would be either A) to offload the entire modifier computation macro to a script (which isn't really the direction I was thinking to go); or B) to somehow get !measure to store only the distance value to a character ability when it is run (preferable). @The Aaron -- is B something that could be easily managed?  If so, I'd appreciate some guidance on how to pull it off;  given the difficulty I had just getting the value to print to chat, I think it's beyond my rudimentary abilities without some help.  If it's not something the API will support, then no worries.  Thank you!
1631547076
The Aaron
Forum Champion
API Scripter
B is definitely possible, but I think A would be more satisfactory in the long run. I've asked Tim to think about meta-script alternatives, so there may be a viable C as well.  For B, the question would be where to store the variable and what to name it. Since it would likely get created as a part of the command, it couldn't be referenced with the @{Character|Attribute} syntax and would need to be expanded on the API side, which implies changing other scripts, or processing your roll template with a script, which basically puts you back in A territory.  What should  your total calculation macro output look like? It might not be that hard to write a snippet that gives you that functionality and is reasonably easy to edit. 
1631549491

Edited 1631557736
David M.
Pro
API Scripter
Scriptcards has built-in functions for token distances for square grids, and can *almost* do it for hexes. Kurt is adding a Sq root function in the next revision. When that is available, I think the scriptcard below would work for Hex(H) [after adding the missing function]. FYI, the "\" below is integer division (rounds down), and scriptcards parses calculations left-to-right (ignoring normal order of operations). The obnoxious 69.xxx and 79.xxx numbers are the X and Y spacing of Hex(H) cells (see here ). EDIT - logic error - see follow up post !script {{ --#title|Distance Between Tokens - Hex(H) --#sourceToken|@{selected|token_id} --#targetToken|@{target|token_id} --#activepage| [*C:playerpageid] --=snapIncr|[*P:snapping_increment] --=dX|[*T:t-left] - [*S:t-left] /[$snapIncr] \ 69.5851274903772 --=dY|[*T:t-top] - [*S:t-top] /[$snapIncr] \ 79.6887899835037 --=dXsq|[$dX]*[$dX] --=dYsq|[$dY]*[$dY] --=sumSquares|[$dXsq]+[$dYsq] --=tokDist| NEED TO ADD SQ ROOT FUNCTION HERE TO TAKE ROOT OF sumSquares --+Num hexes between tokens|[$tokDist] }} Of course like Aaron said, this could be done in a dedicated script as well, either requiring SelectManager so both the selected and target tokens would get passed to the script, or using two targets / two selected tokens.
1631551326
Thanks for the responses!&nbsp; The basic modifier macro is as below: {1d@{selected|shooting}, 1d@{selected|wilddie}}kh1 + @{selected|shooting_mod} [Shooting mod] + (-4) [Base TN] + ?{Misc. Modifers (e.g. flanking, called shot, dim lighting, etc)?|0} [Misc. Modifiers] + (@{selected|wounds}-@{selected|wounds|max}) [Wounds] + @{selected|encumbranceMod} [Encumbrance] + ((@{target|1|size})-(@{selected|size})) [Size Difference] + @{selected|aim} [Aiming] + @{selected|fatigue} [Fatigue] + @{target|1|vulnerable} [Vulnerable] + @{target|1|focus} [Focus Fire] + @{target|1|headdown} [Heads Down] + @{selected|distracted} [Distracted] + @{selected|adrenaline} [Adrenaline] + ([[?{Multiple Actions?|No, 0|2 Actions, (-2+@{selected|multipen})|3 Actions, (-4+@{selected|multipen})|4 Actions, (-6+@{selected|multipen})}]]) [Multiple Actions] + @{selected|lasersight} [Laser Sight] + {(-[[floor((?{Range in hexes to Target 1?|0})/@{Weapons|rangefactor})]] [Range Penalty] + @{selected|ACOG} [ACOG] + @{selected|scope} [Scope]), 0}kl1 + @{target|1|cover} [Cover] The value that this returns is then added together with other weapon- and shot-specific modifiers for the actual shooting roll, an example of which is below: !token-mod --ids @{selected|token_id} --set bar1_value|-3 !roll20AM --audio,nomenu,play|SFX_XCOM_M4_3RB !delay .4 --!cfx MuzzleFlash @{selected|token_id} @{target|1|token_id} !delay .5 --!cfx MuzzleFlash @{selected|token_id} @{target|1|token_id} !delay .6 --!cfx MuzzleFlash @{selected|token_id} @{target|1|token_id} &amp;{template:custom} {{color=@{selected|side}}} {{title=[M4](<a href="https://imgur.com/yAyNzd7.png" rel="nofollow">https://imgur.com/yAyNzd7.png</a>)}} {{subtitle= **Short Burst**}} {{**Target**= @{target|1|token_name}}} {{**Roll**= [[%{Weapons|t1mods} + 1 [Short Burst]]]}} {{``0-3``= ``Hit``}} {{``4-7``= ``Hit + 1 Raise``}} {{``8-11``= ``Hit + 2 Raises...``}} {{ = [Roll Damage](~Weapons|M4-short-damage)}} {{desc= ***@{selected|token_name} cracks off a short burst with their M4 at @{target|1|token_name}!***}} %{Status|RemAim} Aside from the fact that I know next to nothing about the scripting side, part of the reason I'd been leaning away from going all-in on a script is because I like being able to see the breakout of all of the modifiers in the roll-tip.&nbsp; With that in place I can make sure everything's calculating correctly (and the players eventually will have visibility into everything affecting their shot), so removing that visibility would be a tradeoff that I'd need to think about.&nbsp; Also, I've currently got over 3 dozen different weapon/ammo type combinations in the game, most of which have multiple attack types...&nbsp; I'm sure scripting would be able to handle the complexities much more efficiently than the macros I've set up, but by this point I've already done most of the heavy lifting for the macros, and this is really the last missing piece, so if there's a good way to replace: {(-[[floor((?{Range in hexes to Target 1?|0})/@{Weapons|rangefactor})]] [Range Penalty] with: {(-[[floor(@{Weapons|rangetotarget}/@{Weapons|rangefactor})]] [Range Penalty] I'd be so thrilled.
1631557697
David M.
Pro
API Scripter
Got it. Kurt just updated version 1.4.1 of scriptcards (found here ) to add the SQRT function. I had a logic error above, but think it's sorted out. Scriptcards can fire off other api scripts, so you could use chatsetattr to take the calculated distance and put it into the rangetotarget attribute of the Weapons character (see below, last line of macro). Since writing attributes takes some amount of time, you'd have to run the scriptcard to set the attribute, then run one of your macros. If you tried to put it all in one macro, the attribute probably wouldn't be set in time to grab the updated value. So, if you wanted a synchronous one-click option I believe you'd have to do it all on the api side (unless Aaron or Tim have some sneaky way around that). !script {{ --#hidecard|1 --#sourceToken|@{selected|token_id} --#targetToken|@{target|token_id} --#activepage| [*C:playerpageid] --=snapIncr|[*P:snapping_increment] --=dX|[*T:t-left] - [*S:t-left] /[$snapIncr] / 69.5851274903772 {ABS} --=dY|[*T:t-top] - [*S:t-top] /[$snapIncr] / 79.6887899835037 {ABS} --=dXsq|[$dX]*[$dX] --=dYsq|[$dY]*[$dY] --=sumSquares|[$dXsq]+[$dYsq] --=tokDist|[$sumSquares] {SQRT} {FLOOR} --@setattr|_charid @{Weapons|character_id} _rangetotarget|[$tokDist] }} Note that it grabs the map snapping increment from the player ribbon page, so keep that in mind for testing if this is even something you wanted to use.&nbsp;
1631559510
The Aaron
Forum Champion
API Scripter
No really sneaky way I can think of to write and use the attribute.&nbsp; However, showing the full roll up (and even more detail) in a tooltip is completely possible with the API, RecursiveTable just doesn't happen to do that. &nbsp;
1631571089
Thank you!&nbsp; I did a quick bit of testing right now, and it's definitely writing correctly, but I wanted to see if I could get some clarification on the map-snapping increment.&nbsp; Right now the grid cell distance on the player ribbon page is set to 1 hex;&nbsp; is this what the snapping increment is pulling from?&nbsp; The results I'm getting when I measure in a straight vertical line are looking like the squares of the number of hexes: 1 hex = a measurement of 1; 2 hexes = 4; 3 hexes = 9; 4 hexes = 16; 5 hexes = 25; etc. When I measure on the diagonal, they come out as: 1 hex = 1; 2 hexes = 5; 3 hexes = 11; 4 hexes = 20; etc. I'll have to work out an intuitive/unintrusive way of calculating the range before running the firing macro, but this is definitely a big step in the right direction! @The Aaron -- good to know that about the API!&nbsp; Other than seeing some of the amazing tools that you and the other scripters are putting out I really have no clue what it's capable of.
What's it going to take to convince Keith to update powercards with that functionality?
1631592950
Duh.&nbsp; I realized the problem was that I had added ScriptCards from the library, which hasn't incorporated the new square-root functionality, rather than following the link you posted.&nbsp; It's correctly calculating the range in hexes now!
1631625650

Edited 1631625683
David M.
Pro
API Scripter
Jeff, I did notice an issue with certain relative token positions returning incorrect distances that didn't show up in my initial testing. After poking around the web a bit for a more robust hex algorithm, I'd recommend replacing with the following: !script {{ --#hidecard|1 --#sourceToken|@{selected|token_id} --#targetToken|@{target|token_id} --#activepage| [*C:playerpageid] --=snapIncr|[*P:snapping_increment] --:TRANSFORM COORDS WITH SELECTED AS ORIGIN| transform according to <a href="https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid" rel="nofollow">https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid</a> --=Sx|0 --=Sy|0 --=Tx|[*T:t-left] - [*S:t-left] / [$snapIncr] / 69.5851274903772 *10 {FLOOR} /10 {CEIL} --=Ty|[*T:t-top] - [*S:t-top] / [$snapIncr] / 79.6887899835037 *10 {FLOOR} /10 {CEIL} --=u1|[$Sx] --=v1|[$Sx] / 2 {FLOOR} + [$Sy] --=u2|[$Tx] --=v2|[$Tx] / 2 {FLOOR} + [$Ty] --=du|[$u2] - [$u1] --=dv|[$v2] - [$v1] --:PERFORM DISTANCE CALCULATIONS| --&gt;CheckSign|[$du];[$dv] --?[$SameSign] -eq 1|&gt;DistSameSign;[$du];[$dv]|&gt;DistOppositeSign;[$du];[$dv] --:WRITE TO SHEET ATTRIBUTE| --@setattr|_charid @{Weapons|character_id} _rangetotarget|[$tokDist] --X| End Macro --:FUNCTIONS| --:CheckSign| du; dv --=SameSign|0 --?[%1%] -lt 0 -and [%2%] -lt 0|=SameSign;1 --?[%1%] -ge 0 -and [%2%] -ge 0|=SameSign;1 --&lt;| --:DistSameSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --~tokDist|math;max;[$duAbs];[$dvAbs] --&lt;| --:DistOppositeSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --=tokDist|[$duAbs]+[$dvAbs] --&lt;| }}
1631633268
Thanks, David!&nbsp; Need to start work here shortly, but will test this evening. I spent some more time this morning working through the logic of how I have my UI set up and I'm very nearly there.&nbsp; The only thing that's tripping me up now is multiple calls to this rangefinder script in a single macro. For example, autofire allows the player to target more than one enemy token with a single firing action.&nbsp; When the player chooses their target(s), I currently have the rangefinder pulling multiple properties from the target token(s) and storing them in my mule (range, cover, vulnerable, size, etc).&nbsp; They are then pulled into the roll calculation when it runs. The way I had been thinking to handle this for multiple targets is just have multiple rangefinder abilities -- "Targeting1" to target @{target|1|token_id}, "Targeting2" to target @{target|2|token_id}, etc.&nbsp; These would then be called in order depending on how many enemies the player wants to target.&nbsp; So if they click the button to specify 3 targets, it would run: %{Weapons|Targeting1} %{Weapons|Targeting2} %{Weapons|Targeting3} Unfortunately, this doesn't work.&nbsp; Each of the three scripts works fine in isolation, but when I call them together like this, only the first seems to run.&nbsp; The values for the 2nd and 3rd targets don't update.&nbsp; Is this a commonly known issue, where multiple script calls in a single macro interfere with one another?&nbsp; Alternatively, can ScriptCards handle multiple targets in one script?&nbsp; i.e. --#targetToken1|@{target|1|token_id} --#targetToken2|@{target|2|token_id} --#targetToken3|@{target|3|token_id} ...in which case I would just need to set up six versions of the Targeting script, with #1 targeting one enemy, #2 targeting two enemies, #3 targeting three enemies, etc. Thanks again for all of your help!&nbsp; A functional version of this is so close I can taste it!
1631657715

Edited 1631657901
David M.
Pro
API Scripter
Hmm, I don't think having scriptcards fire off a series of character abilities is going to work out well, due to the asynchronous chatsetattr commands that likely wouldn't be completed by the time your abilities tried to fire. Also, there isn't a good way to otherwise pass calculated range info on to a character ability. Here is a version of the above that queries for the number of targets (unfortunately via another chat button - necessary in order to build the string for the total number of attacks - otherwise the chat parser will ask you for all six targets all the time even when only one is needed). The scriptcard then sets a series of abilities on the Weapons sheet, currently named "rangetotarget1", "rangetotarget2", etc., which could later be referenced by your vanilla multiattack macros/abilities (using the existing chat output to make sure you select the targets in the right order). Although, seems like you are almost there already to have the scriptcard just do the rest of the attacks for you, as it's already getting sort of "clicky". You already have all the tokenIDs which scriptcards can use to grab the target/source attributes from. !script {{ --#hidecard|0 --#title|Hex Range Example --#emoteState|0 --#sourceToken|@{selected|token_id} --#activepage| [*C:playerpageid] --=snapIncr|[*P:snapping_increment] --:ASK USER FOR NUMBER OF TARGETS (CHAT BUTTON)| populates a series of str variables e.g. target1, target2... --=numTargets|?{How many Targets?|1,1|2,2|3,3|4,4|5,5|6,6} --&gt;BuildAndAskTargets|[$numTargets.Raw] --:LOOP THROUGH THE TOKEN IDS AND CALC RANGE FOR EACH| then, display the token image, name, and range in chat --=i|0 --:RangeLoop| --=i|[$i]+1 --&amp;thisID|[&amp;target[$i.Raw]] --&gt;CalcRange|[*[&amp;thisID]:t-left];[*[&amp;thisID]:t-top];[$i.Raw] --+[img width=30][*[&amp;thisID]:t-imgsrc][/img]|[b]Target [$i.Raw]:[*[&amp;thisID]:t-name], Range = [$tokDist][/b] --?[$i] -lt [$numTargets]|RangeLoop --X| End Macro --:FUNCTIONS| --:CalcRange| accepts 1-TargetX coord, 2-TargetY coord, 3-Target number --:TRANSFORM COORDS WITH SELECTED AS ORIGIN| transform according to <a href="https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid" rel="nofollow">https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid</a> --=Sx|0 --=Sy|0 --=Tx|[%1%] - [*S:t-left] / [$snapIncr] / 69.5851274903772 *10 {FLOOR} /10 {CEIL} --=Ty|[%2%] - [*S:t-top] / [$snapIncr] / 79.6887899835037 *10 {FLOOR} /10 {CEIL} --=u1|[$Sx] --=v1|[$Sx] / 2 {FLOOR} + [$Sy] --=u2|[$Tx] --=v2|[$Tx] / 2 {FLOOR} + [$Ty] --=du|[$u2] - [$u1] --=dv|[$v2] - [$v1] --:PERFORM DISTANCE CALCULATIONS| --&gt;CheckSign|[$du];[$dv] --?[$SameSign] -eq 1|&gt;DistSameSign;[$du];[$dv]|&gt;DistOppositeSign;[$du];[$dv] --:WRITE TO SHEET ATTRIBUTE(S)| --@setattr|_charid @{Weapons|character_id} _rangetotarget[%3%]|[$tokDist] --&lt;| --:BuildAndAskTargets| --&amp;TargetString| --=targetCount|0 --:TargetLoop| --=targetCount|[$targetCount.Raw] + 1 --&amp;TargetString|+t;target[$targetCount.Raw];Target[$targetCount.Raw] --?[$targetCount.Raw] -lt [%1%]|&gt;AddSeparator --?[$targetCount.Raw] -lt [%1%]|TargetLoop --iPlease click the button below to select the target(s).;Select [%1%] Targets|[&amp;TargetString] --&lt;| --:AddSeparator| --&amp;TargetString|+|| --&lt;| --:CheckSign| du; dv --=SameSign|0 --?[%1%] -lt 0 -and [%2%] -lt 0|=SameSign;1 --?[%1%] -ge 0 -and [%2%] -ge 0|=SameSign;1 --&lt;| --:DistSameSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --~tokDist|math;max;[$duAbs];[$dvAbs] --&lt;| --:DistOppositeSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --=tokDist|[$duAbs]+[$dvAbs] --&lt;| }} Click to play example:
1631682304
Amazing work, David -- thank you!&nbsp; I've been studying this for the past hour or so, referencing the ScriptCards wiki to try and make sense of how it all fits together.&nbsp; I'm starting to build the basic fundamentals of an understanding, though. :) One question I have is -- just to minimize the number of roll queries I feed to my players in favor of having them select options from chat menus -- is it possible to pre-define the numTargets and &amp;TargetString values so that, from the menu below: when the user clicks either the 1, 2, or 3 buttons it will launch only that version of the rangefinder (e.g. the 1-target version, the 2-target version, etc)?&nbsp; It's definitely less efficient coding-wise than the version you put together, but would be a better fit, I think, for the overall flow of the UI I've set up, and would allow me to restrict the maximum number of targets to the capabilities of the weapon.&nbsp; And, if I'm correctly understanding the necessity of having the user click the chat-window button after selecting the number of targets from the roll query, having numTargets and &amp;TargetString pre-defined might remove that step as well? I would just test this myself but there's still too much I don't understand about the ScriptCards syntax to be able to fully parse the BuildAndAskTargets subroutine and understand what the final &amp;TargetString should actually look like. Thanks again for your help -- it must've taken you a good while to research the updated algorithm and write that new code;&nbsp; I really appreciate the guidance and support!
1631708082

Edited 1631708327
David M.
Pro
API Scripter
Sure, this is pretty straightforward, especially since you already have a chat menu set up. This is actually a much better solution than what I did above. At the bottom is an example for a 2-target range finder. You may notice that we removed the BuildAndAskTargets function completely, since each will be hardcoded. To edit for a different number of targets, just adjust the "numTargets" roll variable and add the appropriate number of "&amp;target1", "&amp;target2", etc. string variables. Look for this code block in the macro at the bottom: --:SET NUM TARGETS| --=numTargets|2 --&amp;target1|@{target|Target 1|token_id} --&amp;target2|@{target|Target 2|token_id} Also adjust the title, obviously. Of course, if you don't want the additional chat output with the icons, etc., just update the first line to --#hidecard|1 !script {{ --#hidecard|0 --#title|Hex (H) Range Example - 2 Target --#emoteState|0 --#sourceToken|@{selected|token_id} --#activepage| [*C:playerpageid] --=snapIncr|[*P:snapping_increment] --:SET NUM TARGETS| --=numTargets|2 --&amp;target1|@{target|Target 1|token_id} --&amp;target2|@{target|Target 2|token_id} --:LOOP THROUGH THE TOKEN IDS AND CALC RANGE FOR EACH| then, display the token image, name, and range in chat --=i|0 --:RangeLoop| --=i|[$i]+1 --&amp;thisID|[&amp;target[$i.Raw]] --&gt;CalcRange|[*[&amp;thisID]:t-left];[*[&amp;thisID]:t-top];[$i.Raw] --+[img width=30][*[&amp;thisID]:t-imgsrc][/img]|[b]Target [$i.Raw]:[*[&amp;thisID]:t-name], Range = [$tokDist][/b] --?[$i] -lt [$numTargets]|RangeLoop --X| End Macro --:FUNCTIONS| --:CalcRange| accepts 1-TargetX coord, 2-TargetY coord, 3-Target number --:TRANSFORM COORDS WITH SELECTED AS ORIGIN| transform according to <a href="https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid" rel="nofollow">https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid</a> --=Sx|0 --=Sy|0 --=Tx|[%1%] - [*S:t-left] / [$snapIncr] / 69.5851274903772 *10 {FLOOR} /10 {CEIL} --=Ty|[%2%] - [*S:t-top] / [$snapIncr] / 79.6887899835037 *10 {FLOOR} /10 {CEIL} --=u1|[$Sx] --=v1|[$Sx] / 2 {FLOOR} + [$Sy] --=u2|[$Tx] --=v2|[$Tx] / 2 {FLOOR} + [$Ty] --=du|[$u2] - [$u1] --=dv|[$v2] - [$v1] --:PERFORM DISTANCE CALCULATIONS| --&gt;CheckSign|[$du];[$dv] --?[$SameSign] -eq 1|&gt;DistSameSign;[$du];[$dv]|&gt;DistOppositeSign;[$du];[$dv] --:WRITE TO SHEET ATTRIBUTE(S)| --@setattr|_charid @{Weapons|character_id} _rangetotarget[%3%]|[$tokDist] --&lt;| --:AddSeparator| --&amp;TargetString|+|| --&lt;| --:CheckSign| du; dv --=SameSign|0 --?[%1%] -lt 0 -and [%2%] -lt 0|=SameSign;1 --?[%1%] -ge 0 -and [%2%] -ge 0|=SameSign;1 --&lt;| --:DistSameSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --~tokDist|math;max;[$duAbs];[$dvAbs] --&lt;| --:DistOppositeSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --=tokDist|[$duAbs]+[$dvAbs] --&lt;| }} I know the syntax might be a bit overwhelming right now. Kind of throwing you into the Scriptcards deep end here with a bunch of techniques (loops, subprocedures, nested variable syntax, built-in functions, etc.). It is a very powerful api script, though. I like it because you can do things that would otherwise require writing and installing another script for one-off solutions.&nbsp; If you have specific questions about what any of it is doing, let me know. Here's a couple of explanations of things that might help your understanding: 1) nested variable syntax . The line below sets the "thisID" string variable within our loop to one of the other string variables that we defined when we set up the targets. The value [&amp;target[$i.Raw]] resolves to "target1", "target2", etc., as we are incrementing the "i" roll variable counter. We use ".Raw" to get just the value of the roll variable without all the formatting that normally comes with it (e.g. the yellow background and tooltips).&nbsp; --&amp;thisID|[&amp;target[$i.Raw]] 2) token properties. &nbsp;Normally, token properties can be accessed with the syntax&nbsp; [*ID:attributeName]. The available properties are listed here . In the line below, we get the ID from the thisID string variable, and we are grabbing the token property "left", (the x-coordinate). [*[&amp;thisID]:t-left] 3) subprocedures. &nbsp;The following line calls the "CalcRange" subprocedure and passes three parameters. The general procedure syntax is --&gt;ProcedureName|param1;param2;... In the line below, we are passing the X &amp; Y coordinates of the current token, along with the index of the loop counter. In the CalcRange function (found lower down in the macro), these three parameters are referenced as [%1%], [%2%], and [%3%]. Once the function is completed, the parser returns back to the point in the code that the function was called, then continues line by line. --&gt;CalcRange|[*[&amp;thisID]:t-left];[*[&amp;thisID]:t-top];[$i.Raw] 4) If..then..else conditional . In the line below, we check if the SameSign roll variable is equal to 1. If so, we call the DistSameSign procedure and pass the roll variables du and dv. Else, we call the&nbsp;DistOppositeSign procedure and pass the same two roll variables du and dv. These procedures do the final distance calculation, and assign a value to the&nbsp;tokDist roll variable. --?[$SameSign] -eq 1|&gt;DistSameSign;[$du];[$dv]|&gt;DistOppositeSign;[$du];[$dv] 5) built-in functions. &nbsp;Speaking of those two procedures, you'll notice this line in the DistSameSign procedure --~tokDist|math;max;[$duAbs];[$dvAbs] This is described here in the wiki. Basically just returns the higher of the two variables duAbs and dvAbs, and assigns to the tokDist variable 6) in-line math functions. &nbsp;I don't have a jumplink for this one, but you can search the wiki for the text "Mathematical Functions in Roll Expressions". In the DistSameSign procedure again, you'll see this line: --=duAbs|[%1%] {ABS} Here we are taking the first parameter passed to the procedure and returning the absolute value. Then, that value is assigned to the duAbs roll variable. 7) i nline formatting . This line prints the Scriptcard output. Everything within [b]...[/b] is bolded. Everything within&nbsp;[img width=30]...[/img] is html formatted as an image. The wiki link describes these tags. You'll notice nested variable syntax in a couple spots for the content of these tags. --+[img width=30][*[&amp;thisID]:t-imgsrc][/img]|[b]Target [$i.Raw]:[*[&amp;thisID]:t-name], Range = [$tokDist][/b] Hope that helps!
1631714260
Bingo! I'm definitely more of a learn-by-doing learner, so your breakdown is a huge help!&nbsp; Having the extra info you provided, particularly about the role of &amp;thisID, helped me make a tweak that allows that script to save all the pertinent information about each target to attributes in the Weapons mule so the user only needs to identify the targets one time through the whole firing sequence.&nbsp; This is a massive boost to the player action flow -- I'd previously had to have them select targets a couple times as well as click through several roll queries to determine the modifiers, but it's now down to just the single initial target selection and the shot with a single roll query to determine any miscellaneous modifiers.&nbsp; Really feeling good about this! :) !script {{ --#hidecard|1 --#title|Hex (H) Range Example - 6 Target --#emoteState|0 --#sourceToken|@{selected|token_id} --#activepage| [*C:playerpageid] --=snapIncr|[*P:snapping_increment] --:SET NUM TARGETS| --=numTargets|6 --&amp;target1|@{target|Target 1|token_id} --&amp;name1|@{target|Target 1|character_name} --&amp;size1|@{target|Target 1|size} --&amp;vul1|@{target|Target 1|vulnerable} --&amp;focus1|@{target|Target 1|focus} --&amp;head1|@{target|Target 1|headdown} --&amp;cover1|@{target|Target 1|cover} --&amp;target2|@{target|Target 2|token_id} --&amp;name2|@{target|Target 2|character_name} --&amp;size2|@{target|Target 2|size} --&amp;vul2|@{target|Target 2|vulnerable} --&amp;focus2|@{target|Target 2|focus} --&amp;head2|@{target|Target 2|headdown} --&amp;cover2|@{target|Target 2|cover} --&amp;target3|@{target|Target 3|token_id} --&amp;name3|@{target|Target 3|character_name} --&amp;size3|@{target|Target 3|size} --&amp;vul3|@{target|Target 3|vulnerable} --&amp;focus3|@{target|Target 3|focus} --&amp;head3|@{target|Target 3|headdown} --&amp;cover3|@{target|Target 3|cover} --&amp;target4|@{target|Target 4|token_id} --&amp;name4|@{target|Target 4|character_name} --&amp;size4|@{target|Target 4|size} --&amp;vul4|@{target|Target 4|vulnerable} --&amp;focus4|@{target|Target 4|focus} --&amp;head4|@{target|Target 4|headdown} --&amp;cover4|@{target|Target 4|cover} --&amp;target5|@{target|Target 5|token_id} --&amp;name5|@{target|Target 5|character_name} --&amp;size5|@{target|Target 5|size} --&amp;vul5|@{target|Target 5|vulnerable} --&amp;focus5|@{target|Target 5|focus} --&amp;head5|@{target|Target 5|headdown} --&amp;cover5|@{target|Target 5|cover} --&amp;target6|@{target|Target 6|token_id} --&amp;name6|@{target|Target 6|character_name} --&amp;size6|@{target|Target 6|size} --&amp;vul6|@{target|Target 6|vulnerable} --&amp;focus6|@{target|Target 6|focus} --&amp;head6|@{target|Target 6|headdown} --&amp;cover6|@{target|Target 6|cover} --:LOOP THROUGH THE TOKEN IDS AND CALC RANGE FOR EACH| then, display the token image, name, and range in chat --=i|0 --:RangeLoop| --=i|[$i]+1 --&amp;thisID|[&amp;target[$i.Raw]] --&amp;thisName|[&amp;name[$i.Raw]] --&amp;thisSize|[&amp;size[$i.Raw]] --&amp;thisVul|[&amp;vul[$i.Raw]] --&amp;thisFocus|[&amp;focus[$i.Raw]] --&amp;thisHead|[&amp;head[$i.Raw]] --&amp;thisCover|[&amp;cover[$i.Raw]] --&gt;CalcRange|[*[&amp;thisID]:t-left];[*[&amp;thisID]:t-top];[$i.Raw] --+[img width=30][*[&amp;thisID]:t-imgsrc][/img]|[b]Target [$i.Raw]:[*[&amp;thisID]:t-name], Range = [$tokDist][/b] --?[$i] -lt [$numTargets]|RangeLoop --X| End Macro --:FUNCTIONS| --:CalcRange| accepts 1-TargetX coord, 2-TargetY coord, 3-Target number --:TRANSFORM COORDS WITH SELECTED AS ORIGIN| transform according to <a href="https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid" rel="nofollow">https://stackoverflow.com/questions/15919783/distance-between-2-hexagons-on-hexagon-grid</a> --=Sx|0 --=Sy|0 --=Tx|[%1%] - [*S:t-left] / [$snapIncr] / 69.5851274903772 *10 {FLOOR} /10 {CEIL} --=Ty|[%2%] - [*S:t-top] / [$snapIncr] / 79.6887899835037 *10 {FLOOR} /10 {CEIL} --=u1|[$Sx] --=v1|[$Sx] / 2 {FLOOR} + [$Sy] --=u2|[$Tx] --=v2|[$Tx] / 2 {FLOOR} + [$Ty] --=du|[$u2] - [$u1] --=dv|[$v2] - [$v1] --:PERFORM DISTANCE CALCULATIONS| --&gt;CheckSign|[$du];[$dv] --?[$SameSign] -eq 1|&gt;DistSameSign;[$du];[$dv]|&gt;DistOppositeSign;[$du];[$dv] --:WRITE TO SHEET ATTRIBUTE(S)| --@setattr|_charid @{Weapons|character_id} _silent _rangetotarget[%3%]|[$tokDist] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]|[&amp;thisName] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]_id|[&amp;thisID] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]_size|[&amp;thisSize] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]_vulnerable|[&amp;thisVul] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]_focus|[&amp;thisFocus] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]_headdown|[&amp;thisHead] --@setattr|_charid @{Weapons|character_id} _silent _target[%3%]_cover|[&amp;thisCover] --&lt;| --:AddSeparator| --&amp;TargetString|+|| --&lt;| --:CheckSign| du; dv --=SameSign|0 --?[%1%] -lt 0 -and [%2%] -lt 0|=SameSign;1 --?[%1%] -ge 0 -and [%2%] -ge 0|=SameSign;1 --&lt;| --:DistSameSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --~tokDist|math;max;[$duAbs];[$dvAbs] --&lt;| --:DistOppositeSign| du; dv --=duAbs|[%1%] {ABS} --=dvAbs|[%2%] {ABS} --=tokDist|[$duAbs]+[$dvAbs] --&lt;| }} My only regret is that I wish I'd learned about ScriptCards earlier, as I think I may have designed some systems differently to take advantage of what it can do, but I'll definitely be looking at other places to work it in without necessarily breaking everything I've done so far. Again, thanks so much for the help!
1631717900
David M.
Pro
API Scripter
Great, nice work! And, since it looks like you have all of the attributes you need, it seems like you could just add the actual attack logic in the scriptcard instead of using chatsetattr and needing to fire an additional attack macro. That way, your multiattack could be done with a single click (plus target selection). Of course, you may prefer your existing template formatting, etc. Up to you, but this definitely looks like an upgrade!
1631733162
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Peacekeeper B said: What's it going to take to convince Keith to update powercards with that functionality? 3 years of concerted JavaScript training. Kurt is the author. :D
1631735349
@David -- I did think about that, and it may be a project to look into if I get ambitious, but I've got so many additional variables already built in as macros (dozens of different weapons, each weapon with attack types with their own variables, different ammunition types for certain weapons, etc.) that it would be a pretty big undertaking to rebuild it all.&nbsp; If I was to start anew, I'd take a good hard look at just handling most (if not all) of it in ScriptCards, but until then I'm in if-it-ain't-broke mode. :)
1631735792
David M.
Pro
API Scripter
Fair enough!