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 .
×

[Script, Update] SmartAoE

1792972251

Edited 1793222234
MidNiteShadow7
Pro
API Scripter
SmartAoE was created by David M. (djmoorehead) , and version 1.0.0 is a maintenance release by me, MidNiteShadow7 , that fixes long-standing bugs reported on the Roll20 forums, improves Jumpgate support and tidies up the code, with no breaking changes to existing macros. The full change log is below. Version 1 now appears in the One-Click installer. You can manually download and install it directly from GitHub:&nbsp; <a href="https://github.com/steverobertsuk/roll20-api-scripts/releases/download/SmartAoE-v1.0.0/SmartAoE_v1.0.0.zip" rel="nofollow">https://github.com/steverobertsuk/roll20-api-scripts/releases/download/SmartAoE-v1.0.0/SmartAoE_v1.0.0.zip</a> [1.0.0] – 2026-10-25 First 1.0 release: SmartAoE has been stable on the One-Click library since 0.25, so this version marks it as released. There are no breaking changes; existing macros and AoEs work as before. The author's GitHub repository also has an unreleased 0.31 "Jumpgate compatibility beta" (May 2025) that draws AoEs as pathv2 objects on the Foreground layer. It never reached the One-Click library and is not included here: users reported it did not resolve their errors, and it contains unfinished code (debug logging, a Trigger-Target precision regression). The README's Jumpgate section describes the current workarounds. Added Help handout — a GM-only SmartAoE — Help &amp; Reference handout is created in the Journal and rebuilt every time the script loads: overview, quick start, commands, all !smartaoe parameters (grouped), example macros, the integration API and tips. !smarthelp rebuilds it and whispers a link. Its colours read in both Roll20 light and dark mode. TESTING.md — a manual test checklist for live games, and a .prettierrc / .gitignore matching HealthColors. SmartAoE.ZeroHPMarkers() — public function returning the marker tags used as --zeroHPmarker this session (kept after an AoE is deleted, including --instant AoEs, which are removed before their damage is applied), so other scripts can recognise SmartAoE's 0 HP markers. Errors caught by the chat handler are also logged with their stack trace in the API console, so a line number is available for diagnosis. README: Jumpgate section (layering problem and the --aoeLayer|map workaround), 2024-sheet note, and more known limitations collected from the Roll20 forums. Changed Code refactored to the HealthColors standard (no change to how AoEs are drawn or resolved): header banner with authors and a /* global */ list; 'use strict' ; code grouped under section headers (Constants &amp; state, Observers, Utilities, Chat card output, Saves &amp; inline rolls, Install, AoE links, Tokens &amp; paths, Geometry, Map updates, Event handlers, Roll parsing, Damage &amp; markers, Results output, Help handout, Chat commands, Bootstrap); a JSDoc block on every function; the ~1,400-line chat handler split into one handler per command ( handleCreateAoeCommand , handleTriggerCommand , handleQueryCommand , …) driven by a COMMAND_HANDLERS table; 35 variables that were never declared, and so leaked into the shared Mod sandbox as globals ( pageGridIncrement , originX , i , …), are now declared inside SmartAoE; var replaced by let / const ; about 600 lines of commented-out code and four functions that were never called removed; formatted with Prettier (the same .prettierrc as HealthColors). The comment-only and formatting steps were checked to leave the parsed code identical; the structural steps were checked with ESLint (no undefined variables), the test suites and a live game. Duplicate and dead code removed (about 1,400 fewer lines, with no change in behaviour): circles, squares, walls and cones share one cell-clipping routine ( getCoveredCells ) instead of four near-identical copies of about 300 lines each; each shape now only supplies its inside test and edge crossings; the 56 abilities of the AoEControlToken character come from one table ( CONTROL_TOKEN_ABILITIES ) instead of 56 separate createObj blocks; roll results for damage rolls and targets are formatted by one function ( attachRollResults ) instead of two inline copies; the chat commands find the caller with one getCaller helper instead of seven copies of the same block; the chat card's alternating row colours, P/F badge and damage cell are each built in one place; get5eConePt (a copy of getConeEndPt ), processInlinerolls , sendChatNoarchive and processDamageMessages (unused) removed, and processInlinerolls2 renamed substituteInlineRolls ; clearCache only ever cleared everything, so it is now resetLinks() , which install also uses; makeAoELink takes the single path id it is always given; the flat-cone angle test spells out the short-way-round angle; unused parameters, variables and return values removed throughout (for example the obj parameter of updateMapWithAoE and the hideformula flags of the chat-card rows). Checked by comparing the old and new code: 28,500 random shape calculations give the same squares, and seven end-to-end scenarios in a mocked game (every shape, saves, resistances, apply buttons, markers, origin rotation, token moves, query, remove, clear-cache) give identical chat output, objects and state. Fixed Commands with nothing selected (or the wrong thing selected) no longer throw. !smarttrigger on a token that isn't part of an AoE, !smartquery and !smartrotateorigin with nothing selected, and !smartremove with an unknown id now whisper what to do instead of "Cannot read properties of undefined". A target whose character no longer exists is skipped instead of breaking the whole trigger. An AoE with no --saveFormula and no --noSave no longer does nothing when triggered. Each target sent an empty roll that Roll20 never answered, so no card was posted and no damage applied, with no error. It is now treated as no save: every target takes full damage. Found during live testing. --noSave|false no longer breaks saves. The noSave option had no break , so it fell through into saveFormula and set the save formula to the text false . Explicit colours from other scripts are kept. When another script called SmartAoE with --playerID , if (getFillColor = true) (an assignment, not a comparison) always replaced the --aoeColor / --aoeOutlineColor it passed with the player's colour. Names containing a backtick no longer break the chat-card apply buttons (the escape used a regular expression as the replacement text). A zero-length wall segment no longer produces NaN distances (a lowercase p was used for the point P ). applyMathRule with an unknown operator returns the value unchanged instead of the previous call's result. --damageFormula2 now works with plain numbers and in every combination. A plain number in formula 2 overwrote formula 1's damage, and formula 2 was only rolled when both formulas were inline rolls, so a number + roll, roll + number or number + number pair silently lost formula 2. Each formula is now rolled independently, whether it is a number or a roll. Formula 2 shows on the chat card without formula 1. The card only showed damage when formula 1 had a result; it now shows every damage roll with a non-zero result. Negative plain-number damage (healing) , e.g. --damageFormula1|-5 , is now rolled as [[0 - 5]] , because Roll20 can't start an inline roll with a negative number. A non-numeric plain value counts as 0 instead of producing a broken [[NaN]] roll. Arithmetic and grouping in inline damage rolls , e.g. --damageFormula1|[[2*13d6]] ( forum report ). libInline HTML-encodes roll expressions, and SmartAoE only decoded [ and ] , so the formula was re-rolled as [[2&amp;#42;13d6]] and dealt 2 damage. * , {…} groups, &gt; / &lt; comparisons and quotes now survive. Trigger-Target no longer breaks because of a stale AoE. If any AoE on the page referred to a square that had been deleted (by hand, by copying pages, etc.), !smartquery / Trigger-Target stopped with "Cannot read properties of undefined (reading 'get')" for every target ( reported several times ; the workaround was !smartclearcache ). Missing squares are now skipped, and an unknown target token or page is handled cleanly. !smarttrigger in Jumpgate: the AoE squares sit on top of the control token in Jumpgate ( forum , forum , forum ), so clicking the control token often selects one of the squares instead, and the trigger then failed with "Cannot read properties of undefined (reading 'get')". A selected AoE square now triggers its own AoE. Found in live testing. !smarttrigger &lt;controlTokenID&gt; without a target id crashed the same way; it now triggers everything in that AoE. An unknown target id, or nothing usable selected, now gets a clear message instead of an error. --removeAtZero now removes tokens. The 0 HP check compared the array of new bar values to 0 , which is never true, so no token was ever removed (broken since 0.25). It now checks the last bar in --bar , the same test --zeroHPmarker uses; a temporary-HP bar reaching 0 does not remove the token. "( @max )" healing note now appears. The healed message compared two arrays ( newHP===maxHP ), so the note was never shown. It now appears when every healed bar is at its max. Healing that cascades to a second or third bar now passes on only the leftover healing; before, each later bar received the full heal amount. A heal also no longer lowers a bar that is already above its max (e.g. temporary HP) down to the max. Adding status markers ( --conditionPass , --conditionFail , --zeroHPmarker , --casterCondition , chat-button markers): markers in a list are no longer skipped (items were removed from the list while it was being looped over); an existing marker is matched by its whole tag, so red no longer counts as present because of red@2 or redbeard ; a marker repeated in the list is added once; no stray leading or trailing comma is left in the token's statusmarkers ; observers ( ObserveTokenChange ) are only notified when a marker was actually added. Removing status markers now removes markers that carry a number badge ( skull@3 is removed by skull ) and ignores spaces after commas; observers are only notified when something was removed. Adding or removing markers on a token that no longer exists no longer throws. Squares at the edge of an AoE are no longer missed when the origin is off-centre. For circles, squares and cones from a corner or face of a large token, the area searched for covered squares was shifted by the origin offset, and the vertical offset and size were ignored. Edge squares could be left out. The search now covers the whole shape. A circle smaller than one square now covers that square. A small circle sitting inside a square never crosses its edges, so no square was marked and the AoE was empty. Two AoEs from the same caster post separate cards. When a caster's AoEs were triggered together, each card also included the earlier AoEs' damage rows and targets. !smartrotateorigin redraws only the AoE it rotates. It redrew every AoE linked to the token with that AoE's offset, and it no longer throws if the caster token has been deleted. Control token sides: --controlTokSide accepted any number because of an || that should have been &amp;&amp; , then failed on a side that didn't exist. An invalid side is now reported, and the AoE stops instead of crashing. Deleting old squares no longer clears the AoE being created. deleteLinkedPaths reused the shared path variable that AoE creation was still using. Damage maths uses numbers. Roll results are arrays, so a rule such as +2 joined them as text ( [5] + 2 gave 52 ), and a line with two rolls compared as NaN . Damage now uses the line's total and a save uses its first roll. In normal use SmartAoE passes one roll per line, so this is a safeguard. !smartpinggm / !smartpingall are matched properly (the old pattern was a character class, not "gm or all"), and only one command runs per chat message. Smaller fixes: a direction between two identical points is 0 instead of NaN ; control points at angles outside 0–360° are calculated instead of undefined; colours have every non-hex character stripped, not just the first; the collections macro is skipped with a log line if no GM is found, instead of throwing; whispers from token spawning quote the player's name; &amp; used where &amp;&amp; was meant; and more variables are now local to their functions instead of shared. Startup error reporting: if creating the default AoEControlToken character or AoEGenerator macro failed, the error handler itself threw a ReferenceError ( charName was declared inside the try block), hiding the real error. The real error is now logged. Let me know if you have any problems.
Hi MidNiteShadow7. Thanks for taking care of smartAoE. I'm running into several issue. It's probably a syntax error on my part. I'm running on jumpgate, using Beacon sheets for both PC and NPC's and using the ZIP version of your 1.0 release. I'll paste the code at the bottom and list the issues I'm seeing here: 1- After executing the code block, I get the following error:&nbsp;(From SmartAoE): Resource 'lvl3_slots_expended' is not found. When I remove the --resource command from the code block, the error goes away. But I confirmed that the 'lvl3_slots_expended' attribute exists on the beacon sheet. Any ideas as to why smartAoE is not reading this attribute? The following errors occur after I remove the --resource command 2- The NPC tokens making the saving throw are not adding the "dexterity_save_bonus" to my --saveformula command. I see that it rolls a d20, but does not include the save bonus. Just like item #1 above, I confirmed that this attribute exits. Not sure why smartAoE is not reading it from the Beacon sheet. 3- I don't think the --resistAttr, --vulnerableAttr, and --immunityAttr commands are working for Beacon sheets either. I noticed that none of these are capturing the fact that the NPC's have any of these attribute. For example, casting "fireball" on the fire giant subtracted all of the damage from the tokens HP. It should have been 0 due to immunity. These are the errors I'm seeing so far. Again, I'm sure I'm using incorrect syntax.&nbsp; Any help would be appreciated !smartaoe {{ &nbsp; --title|Fireball &nbsp; --leftsub|Slot level ?{Cast at what level?|3,3|4,4|5,5|6,6|7,7|8,8|9,9} &nbsp; --rightsub|DC @{selected|spell_save_dc} DEX &nbsp; --radius|20ft &nbsp; --aoetype|circle, float &nbsp; --instant|1 &nbsp; --forceIntersection|1 &nbsp; --mingridarea|0.25 &nbsp; --mintokarea|0.25 &nbsp; --fx|burn-fire &nbsp; --aoeColor|#99000050 &nbsp; --aoeOutlineColor|#99000050 &nbsp; --controlTokName|AoEControlToken &nbsp; --triggerSound|fireball &nbsp; --aoeLayer|map &nbsp; --dc|@{selected|spell_save_dc} &nbsp; --saveformula|?{SaveType|Normal,5eDEX|Advantage,&lt;&lt;2d20kh1 + a{dexterity_save_bonus&amp;#125;&gt;&gt;|Disadvantage,&lt;&lt;2d20kl1 + a{dexterity_save_bonus&amp;#125;&gt;&gt;} &nbsp; --damageformula1|[[(5+?{Cast at what level?})d6]] &nbsp; --damagetype1|fire &nbsp; --bar|1 &nbsp; --autoapply|1 &nbsp; --conditionfail|red &nbsp; --conditionpass|green &nbsp; --zerohpmarker|dead &nbsp; --resistAttr|npc_resistances &nbsp; --vulnerableAttr|npc_vulnerabilities &nbsp; --immunityAttr|npc_immunities }}
Hi Pak, I have made some updates to version 1.0.0. I also updated the OP (including the ZIP file contents). If that is cached, you can download it from Dropbox instead:&nbsp; <a href="https://www.dropbox.com/scl/fi/cuw2j91pq8n81e66zoi7x/SmartAoE_v1.0.0.zip?rlkey=591lxqgvdep3til72y4w7x9iq&amp;dl=0" rel="nofollow">https://www.dropbox.com/scl/fi/cuw2j91pq8n81e66zoi7x/SmartAoE_v1.0.0.zip?rlkey=591lxqgvdep3til72y4w7x9iq&amp;dl=0</a> Let me know if you have any further problems.
1793197915

Edited 1793197954
Pak
Pro
Hi MidNiteShadow7. I grabbed the Dropbox version and I'm still getting mixed results.&nbsp; I'm happy to report issue #2 was resolved.&nbsp; SmartAoE saving throw rolls now include the appropriate save bonus. Issue #1 is still not acting as expected. Its almost as if its seeing the definition of "expended" as the opposite of its true intent. It reads 0 expended as having no slots at all. Below is the error I get when I 0 expended slots. Not enough lvl3_slots_expended! Current lvl3_slots_expended = 0, Cost = 1 But when I use a slot on the beacon sheet, I'm able to run the smartAoE. And get the following: lvl3_slots_expended reduced by 1. Current value = 0 So it subtracted the expended slot attribute by 1 instead of adding it.&nbsp; And now I'm back to 0 expended slots which will trigger another "not enough slots" error if I run the AoE again. I would expect that it should see the 0 slots expended as the selected token having all of its slots left instead it sees it as not having any slots left. Issue #3 is still a problem... at least for invulnerability.&nbsp; I tried fireball on a Fire Giant and it is still subtracting fire damage from the fire giant token. I have not attempted to test resistance nor vulnerability yet...
[1.0.1] – 2026-10-28 Further D&amp;D 2024 (Beacon) sheet fixes (mentioned above).&nbsp;There are no breaking changes. You can manually download the update from GitHub:&nbsp; <a href="https://github.com/steverobertsuk/roll20-api-scripts/releases/download/SmartAoE-v1.0.1/SmartAoE_v1.0.1.zip" rel="nofollow">https://github.com/steverobertsuk/roll20-api-scripts/releases/download/SmartAoE-v1.0.1/SmartAoE_v1.0.1.zip</a> Version 1.0.1 isn't in the One-Click installer yet, but it's submitted. Added Grid rules by game system , in the README and the help handout — the --minGridArea , --minTokArea , shape and origin settings that match D&amp;D 5e 2014 and 2024, Pathfinder 1e and 2e, Starfinder and other systems, with notes on how creature size affects hits (a Tiny creature in a counted square is hit). Changed The help handout's 2024 (Beacon) sheets tip no longer says the 5eSTR … 5eCHA shortcuts need a custom --saveFormula on the 2024 sheet (they have worked there since 1.0.0); it now gives the Sandbox v1.5 and spell-slot notes. 2024-sheet spell slots count up without a limit. On a 2024 sheet, --resource|lvl3_slots_expended (any level) now counts the slots used up to lvl3_slots_total when no limit is given. In 1.0.0 it counted down, so with no slots used it always said there weren't enough, and casting with one slot used took it back to 0. 2014 sheets, where lvlN_slots_expended holds the slots left, still count down. Fixed Empty leftover attributes no longer hide 2024 sheet values. A 2024 character can carry legacy attribute objects with the same names as its sheet values, for example after moving a 2014 character to the 2024 sheet or when another mod creates them. An empty npc_immunities (or npc_resistances , npc_vulnerabilities , --ignore or --resource attribute) was used as the value, so a Fire Giant took full fire damage even though a chat roll showed "Fire". An empty legacy attribute is now skipped and the sheet's value used. !smarttrigger before the control token has moved no longer stops with "Cannot read properties of undefined (reading 'x')". The AoE has no squares yet, so SmartAoE whispers to move the control token first. Resistances to nonmagical attacks no longer apply to AoEs. The damage-type check was a plain text search, so a 2014 stat block's "bludgeoning, piercing, and slashing from nonmagical attacks" (a Gargoyle, many devils and lycanthropes) halved or cancelled a bludgeoning AoE. AoEs count as magical, as they already did for Magic Resistance, so these damage types are now ignored. Other damage types on the same line still apply. Magic Resistance is detected on 2024 sheets. A creature with a Magic Resistance feature, such as a Yuan-ti, now rolls its save twice and keeps the higher, shown as A on the card, as on the 2014 sheet. The 2024 sheet keeps its features in one store object, which SmartAoE read as "[object Object]". Only feature names are checked, so text elsewhere on the sheet, such as an item's description, doesn't count.
TRULY appreciated MidNiteShadow7!!!