Quick Summary: The Radar script creates an animated wavefront from a selected token that reveals visible and potentially invisible tokens on the map, with optional character or token property filters. Output including directional and distance information of qualifying tokens is whispered in the chat to the player calling the script, via the default template. Static screenshot: Uses: Game system and character sheet agnostic, anything where you want to "ping" nearby tokens within a given range. The tokens can be on any/all layers of the VTT (user configurable), including "invisible" layers such as GM Info Overlay or Dynamic Lighting layers. Modern or sci-fi games where radar and/or scanner technology exists, or fantasy games where magic or abilities allow non vision-based sensing of creatures (e.g. blindsense, tremorsense, divine sight, etc.). YouTube Video by Nick O. Nick O. put together a great video highlighting a couple of example use cases of the script! Check it out here ! Support my Patreon I've been asked by a few folks if they can provide support in appreciation for a script or other help. If you find yourself falling into that group, too, then thank you! If not, no worries - it's not why I do this. I just want everyone to have as much fun as possible playing the games that they love! Disclaimer: Patreon campaigns are not affiliated with Roll20. Contributions are entirely voluntary and Roll20 cannot provide support or refunds for contributions. GitHub link: <a href="https://github.com/djmoorehead/roll20-api-scripts/blob/master/Radar/Radar.js" rel="nofollow">https://github.com/djmoorehead/roll20-api-scripts/blob/master/Radar/Radar.js</a> Version history: 0.1 - Initial release. Check it out! Feedback appreciated :) 0.2 - Added support for gridless play. NOTE: for results to display properly, the --units command must be included. Otherwise will be infinity. 0.3 - Allows --range command to accept units other than pixels. Converts the range to pixels using Page Scale & Cell Width settings for the page 0.4.1 - Added option to send the GM a copy of chat output, via the --silent configuration. Added support for custom html colors in addition to the four "named" colors 0.5 - Allows Radar to be called from within another api script 0.6 - Added the --visible command to toggle whether the wavefront is drawn on the VTT (e.g. !radar --visible|false). If false, no animation, but the output will still be sent to chat 0.7 - No more default template. Added square wavefront and custom color options. Added cone support. Added graphical output with options, added filter options ("ignore only" tags, exact matches, comparative filters, wildcard filters), added --groupBy command. See this post for detailed breakdown of syntax and examples. 0.8 - Fixed bug with 5e cones. Added pf/3.5/5e distance calculation options. See this post for more details. 0.9 - Composite filter groups (tokens meeting multiple filter conditions form a new composite group). Bug fix for multiple comparisons against the same value (e.g. hp>0 and hp<0). See this post for details. 0.10 - Bug fix to re-enable gridless map support 0.11 - Added optional --public output command Setup: Create a character named " RadarPing " Set the default token to a 1x1 unit transparent png , with a 0 ft aura NOT visible to all players . (a) Leave the " represents " property of the token blank. (b) Leave " edited & controlled by " property blank. This will be assigned dynamically to the player calling the script Create a macro or ability using the commands & arguments described below. Select a token as the source of the radar prior to activating the macro Output: Animated wavefront extending from the selected token out to the max range. User configurable animation properties. Temporary "Ping" of target tokens satisfying filter criteria (if any). (a) The ping takes the form of an instance of the transparent RadarPing token at the target token location, with a semi-configurable colored aura which should only be visible to the calling player and anyone with GM privileges. (b) The api can handle filters based on character attributes OR token properties, including negative filters for tokens that cannot be scanned (e.g. cloaking technology, nondetection spells, etc.) If no filters used, will output a numbered list of all tokens within range, along with directional information and distance from the origin token If filters are used, the output will be grouped by filter keyword (unless --groupBy|false is used), then sequentially numbered along with directional information and distance from the origin token Silent mode is also available, with gives VTT visuals but no chat template output There is optional graphical output added in version 0.7, with customizable style features Description of optional commands/arguments !radar {{
--range| <# <optional units> > //Default=350. How far the radar range extends, in pixels. Measured from center of selected token. Accepts inline rolls e.g. [[ 5*70 ]]
//optionally, can specify units in "u" or the units in Page settings. e.g. "60u" or "60ft" --wavetype| <circle/square/5e <optional coneDirection/tokenID coneAngle> //default=circle. range determined by pythagorean theorem
//square. Diagonals squares count as one unit
//5e. will produce a cone of ~53.14 deg. The width of cone is equal to the cone legnth.
//coneDirection - the angle of the center of the cone (clockwise positive, 0deg is straight up). If a tokenID is entered, the angle between the source and target token will be used
//coneAngle - the angle of the cone in degrees --wavecolor| < #RRGGBBaa> //default= red (#FF0000). standard html color notation #RRGGBBaa, where RGB are hexidecimal digits (0-F) and aa is the opacity. If aa is omitted, then the wavefront will be at 100% opacity. --spacing| <#> //Default=35. The spacing between waves, in pixels (lower number = slower wavefront) --wavedelay| <#> //Default=50. How much time to wait before next wave, in ms (higher number = slower wavefront)
--wavelife| <#> //Default=200. How long each wave wil remain on screen, in ms (higher number = more waves present at any one time)
--pinglife| <#> //Default=2000. How long each "RadarPing" token will remain on screen, in ms
--layers| <gmlayer, objects, walls, map> //Default="gmlayer, objects". Which layers to look for tokens. any or all may be included
//accepts "gmlayer" or "gm"
//accepts "objects" or "tokens"
//accepts "walls" or "dl"
//accepts "map"
//NOTE: if target tokens are found on DL(walls) or map layer, output will be in red text, indicating token is invisible to selected token
--LoS| <yes/true/1> or <no/false/0> //Default=false. Will DL walls block radar sensor if completely obscured? To block, all corners and the center of the target token must be in LoS with the center of origin token
--title| <text> //Default="Radar Ping Results". The title of the output template. e.g. "Divine Sense", "Tremorsense"
--silent| <yes/true/1> or <no/false/0> <gm> //Default=false and no additional GM output if player calls. If true, no output template will be sent to chat. animations only.
// optional "gm" flag to send result output to gm chat
//e.g. --silent| yes gm //no chat output for player, output is whispered to GM
//e.g. --silent| no gm //both gm and player receive whispered chat output
//e.g. --silent| yes //no output for anyone
--units| <u/units/squares/square/hexes/hex, Optional 3.5/pf/5e>
//if optional 5e or omitted, this will only affect display output (5e will use diag=1sq)
//if pf or 3.5 is used, then both the display output AND the determination of if tok is within range will use PF-style calcs, where every other diag=1.5sq
//for GRIDLESS MAP, this command must be included or else all results will be INFINITY
// however, the optional parameters will only work with a gridded map. --visible| <yes/true/1> or <no/false/0> //Default=true. Set to false/no/0 to prevent the drawing of the wavefront animation --graphoptions| < grid/circles/rings/reticle > //Default is a plain background. Can add graphical elements to display. Circles/Rings are interchangeable aliases
--output| < graph, table, compact > //Default=table. Can include one or all of these elements. Compact attempts to put the table output on a single line
--groupby| < yes/true/1> or <no/false/0 > //Default=true. When a charFilter or tokFilter is used, this flag determines if table results are grouped by filter attribute --public| < yes/true/1> or <no/false/0 > //Default=false (whisper to player). If true, will display output to chat publicly //--------------------------------------------------------------------------------------
// THE FOLLOWING TWO COMMANDS ARE USED WHEN CALLING RADAR FROM ANOTHER API SCRIPT
// e.g. sendChat(scriptName, `!radar --selectedID|${msg.selected[0]._id} --playerID|${msg.playerid}`
--selectedID| <ID of the selected token> //used to identify the radar origin token (use msg.selected[0]._id from your parent script)
--playerID| <ID of the player calling the script> //used to determine who gets whispered the results (use msg.playerid from your parent script)
//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
// ONLY CHOOSE ONE OF THESE TWO OPTIONAL FILTERS: ( tokfilter or charfilter )
//if used, output template will group by filter keyword
--tokfilter| <property>:<optional matchType><filterText1<optional #color>,..., -exclude text>
e.g. "--tokfilter| bar3_value:celestial, fiend, undead, -cloak"
//only pings tokens where bar3_value contains either celestial, fiend, or undead .
// Ignore tokens with " cloak " in the bar3_value property
//the only valid token <properties> are:
// " bar1_value "
// " bar2_value "
// " bar3_value "
// " bar1_max "
// " bar2_max "
// " bar3_max "
// " gmnotes " //optional matchTypes:
//" @ " - exact match
//" > " - greater than (numeric only)
//" < " - less than (numeric only)
//"*" - any value - no filterText needed e.g. with optional color coded auras by filter group
"--tokfilter| bar3_value:celestial #yellow , fiend#red, undead #99009f , -cloak"
//Valid aura colors:
// " #red " ( default )
// " #green "
// " #blue "
// " #yellow "
// "#0000ff" format (any custom html color accepted)
--charfilter| <attribute>:<optional matchType><filterText1, filterText2, -excludeText>
e.g. "--charfilter| npc_type:celestial, fiend, undead, -nondetection"
//only ping tokens where npc_type attribute contains either celestial, fiend, or undead.
// Ignore tokens with " nondetection "in the npc_type attribute
//any text may be entered for <attribute>
//if attribute doesn't exist on character sheet, token is ignored
//if token does not represent a character, it is ignored if this filter is used
//optional matchTypes:
//" @ " - exact match
//" > " - greater than (numeric only)
//" < " - less than (numeric only)
//" * " - any value - no filterText needed
e.g. with optional color coded auras by filter group
"--charfilter| npc_type:celestial#yellow, fiend#red, undead#blue, -nondetection"
//same colors available as for tokfilter Example: (D&D 5E "Divine Sense") Here is an example simulating the 5E D&D "Divine Sense" paladin ability, which allows the character to sense the presence of any celestials, fiends, and undead within 60ft, that are not behind total cover . Note: I could have omitted the wave & ping commands in the api call below since they are currently just set to the default values, but I included here for reference anyway. This example checks all tokens on the GM, token, and map layers. If within range and linked to a character sheet with the "npc_type" attribute that matches the keywords (and doesn't match the "nondetection" keyword) (and not behind total cover), the tokens will ping with color-coded auras. Grouped directional and distance output will be sent to the chat. !radar {{
--range| [[ 12*70]]
--wavespacing| 35
--wavedelay| 50
--wavelife| 200
--pinglife| 3000
--layers| gm, token, map
--charfilter| npc_type: celestial#yellow, fiend#red, undead#blue, -nondetection
--LoS| yes
--title| Divine Sense
--silent| no
--units| u
}} Scenario: Our paladin is in a tavern and thinks something might be up. He uses his Divine Sense ability and notices two ghosts lurking in the halls, his pegasus just outside the doorway, and an invisible Imp (on the GM layer) in the main room. Note that I copied the DL lines to the map layer so they would be visible for this example so you can better see the LoS interaction. Also note that he could not sense the 3rd ghost in the storage area as it has full cover behind the tavern wall. Finally, the invisible imp is displayed in the chat output with red text, indicating it is on a layer that he cannot see with vision (e.g. walls or GM layer). Animated GIF from GM view. Click image below to play animation: Here is the same scenario from the player's perspective. A little harder to see the pings due to DL and the partial cover of the tokens, so the chat output is perhaps more useful in this case. Note that the ping from the invisible imp still shows up for him (as it has concealment and not cover). Click image to play: A few notes: Note 1 -- LoS: Lines of sight/effect are checked for each token against each wall segment within range. Five lines are check for each token, extending from the origin token to five points within the target token (the center and four corners of the token). See example below for two of the tokens within range. The pegasus is only partially covered (2 intersections), while the ghost in the storage room is completely blocked (5 intersections). If even one test line segment has a clear path, the token will be considered "ping-able", assuming it meets all other criteria. Didn't draw the lines, but the center top ghost in the image below has one corner exposed, so it is visible to !radar. Note 2 -- Distances: Distances output to the chat are measured from the center of the origin token to the center of the "nearest cell" of the target token. For 1x1 tokens this is trivial, but for larger tokens, just keep that in mind. Also, even if the larger token has partial cover, the distance is measured to the nearest cell while disregarding that cover. Sorry, the code is confusing enough already. :) For example, our pegasus above is measured to the center of the lower left square. This should still work for hexes or different grid sizes, but to be honest I have only done minimal testing with those configurations. Note 3 -- Rotated Tokens with non-uniform sizing : The VTT gets a little weird with higher aspect ratio tokens (i.e. width not equal to height) that are rotated, as the bounding box for the token may no longer line up with the physical token boundaries. Expect to see some larger-than-anticipated pings and some distances/locations that may be slightly off from where they "should" be. If it is a major problem, maybe I can look into it some more, but just wanted to get this out and tested for now. That's about it. A lot of work for a pretty niche application, but it was fun figuring out and I hope some folks find a use for it!