Repository navigation
chore(SmartAoE): Release version 1.0.0 - #2354
Merged
Merged
Conversation
…very combination The second damage formula only worked when both formulas were rolls. A plain number in formula 2 was parsed into damageBase1, overwriting formula 1's damage, and formula 2 was dropped whenever formula 1 was a plain number. The chat card also only showed damage when formula 1 had a result, so formula 2 on its own never appeared. Roll each formula independently, as an inline roll or a plain number, and store formula 2's plain number in damageBase2. Build the card's damage rows with buildDamageRows, which shows every damage roll with a non-zero result. Plain-number damage goes through baseDamageFormula, which writes negative amounts (healing) as "0 - n" because Roll20 cannot start an inline roll with a negative number. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
A damage formula such as --damageFormula1|[[2*13d6]] dealt 2 damage. libInline HTML-encodes roll expressions and SmartAoE only decoded [ and ], so the formula was re-rolled as [[2&Roll20#42;13d6]]. Reported on the Roll20 forum: https://app.roll20.net/forum/post/11924759/help-smartaoe-refuses-to-recognise-star-as-a-multiplier-any-ideas Add decodeInlineExpression, which decodes the numeric, hex and named entities libInline uses (including its full-width "<"), and use it for both damage formulas, so *, {...} groups and > or < comparisons work. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
If any AoE on the page referred to a square that had since been deleted (by hand, by copying pages, and so on), !smartquery and the Trigger-Target token action stopped with "Cannot read properties of undefined (reading 'get')" for every target. This was reported several times on the forum thread: https://app.roll20.net/forum/post/10485883/script-smartaoe-graphical-interface-for-implementing-aoes-on-gridded-maps/?pagenum=16 getAoELinks and the path checks now skip squares, tokens and pages that no longer exist. In Jumpgate the AoE squares sit above the control token, so clicking the reticle can select a square instead and !smarttrigger then failed: https://app.roll20.net/forum/post/12193888/script-smartaoe-anyone-using-it-in-a-jumpgate-campaign-successfully https://app.roll20.net/forum/post/12349884/smartaoe-controltoken-spawns-behind-its-colored-squares https://app.roll20.net/forum/post/12400598/smartaoe-and-jumpgate findTriggerToken maps a selected square back to its AoE's control token. "!smarttrigger <controlTokenID>" without a target id, which also crashed, now triggers everything in that AoE; an unknown target id or an empty selection is reported instead of throwing. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
…Zero Healing that cascaded to a second or third bar gave each later bar the full heal rather than what was left over, and could pull a bar that was above its max (temporary HP) down to the max. The "(@max)" note compared two arrays with ===, so it never appeared, and --removeAtZero compared the array of new bar values with 0, so no token was ever removed. Heal each bar up to its max and pass only the remainder on, leaving bars already above max alone; show "(@max)" when every bar is full; and remove the token when its last bar reaches 0. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Adding markers (--conditionPass, --conditionFail, --zeroHPmarker, --casterCondition and the chat-card buttons) spliced items out of the list while looping over it, so some were skipped. A marker counted as present when it appeared anywhere in the string, so "red" was treated as present because of "red@2" or "redbeard", and stray leading or trailing commas were left in statusmarkers. Removing markers missed markers with a number badge and did not allow for spaces after commas. Compare whole marker tags without their @Number badge (markerTag) on trimmed entries (splitStatusMarkers), add each new marker once, write the list back without stray commas, and only notify observers when a marker was actually added or removed. Adding or removing markers on a token that no longer exists no longer throws. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Other scripts, HealthColors in particular, need to know which status markers SmartAoE uses as its 0 HP marker so they do not add their own dead marker on top. They could only read the AoEs saved in state, which misses --instant AoEs because those are deleted before their damage is applied. Remember every --zeroHPmarker when an AoE is created and before damage is applied, and expose the list as SmartAoE.ZeroHPMarkers(). Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
If creating the default AoEControlToken character or the AoEGenerator macro failed, the error handler threw a ReferenceError of its own, because the names it reported were declared inside the try block, and hid the real error. Declare them before the try block. Errors raised by chat commands were only whispered as a one-line message. Also log the stack trace to the API console for diagnosis. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Add the same .prettierrc as HealthColors (print width 120, single quotes, trailing commas, two-space indentation, LF line endings), so the script can be formatted consistently, and a .gitignore for zip archives and alpha/beta builds. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Bring the code in line with HealthColors without changing how AoEs are drawn or resolved: - add a header banner with the authors and a /* global */ list, run in strict mode, and group the code under section headers; - add a JSDoc block to every function; - split the 1,400-line chat handler into one handler per command (handleCreateAoeCommand, handleTriggerCommand, handleQueryCommand and so on), dispatched from a COMMAND_HANDLERS table; - declare the 35 working variables that leaked into the shared Mod sandbox as globals inside SmartAoE, and replace var with let/const; - remove about 600 lines of commented-out code and four functions that were never called, and three duplicate object keys whose earlier values were already overridden; - format with Prettier. An end-to-end comparison in a mocked game (every shape, saves, resistances, apply buttons, markers, origin rotation, token moves, query, remove and clear-cache) gives identical chat output, objects and state before and after this change. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
…her scripts The noSave option had no break, so --noSave|false fell through into saveFormula and set the save formula to the text "false". When another script called SmartAoE with --playerID, "if (getFillColor = true)" and "if (getOutlineColor = true)" assigned rather than compared, so the player's colour always replaced colours passed explicitly. Both were flagged by ESLint during the restructuring. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
…own math rules - buildSaveRow escaped backticks in names with a regular expression as the replacement text, which broke the chat card's apply buttons. - distToSegmentSquared used a lowercase p for the point P when the segment had zero length, giving NaN distances. - applyMathRule wrote to a shared variable, so an unknown operator returned the previous call's result instead of the value unchanged. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
…ands Several commands threw when given nothing usable: - !smarttrigger on a token that is not part of an AoE now whispers that it is not part of a SmartAoE; - !smartquery with nothing selected no longer reads an undefined selection, and returns after its "must select" message; - !smartrotateorigin with nothing selected asks for a selection, and a token without AoEs is left alone; - !smartremove with an unknown id reports that the token was not found. A target whose character no longer exists is skipped rather than breaking the results card. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Create a GM-only "SmartAoE — Help & Reference" handout in the Journal with an overview, a quick start, the chat commands, every !smartaoe parameter grouped by topic, example macros, notes on other scripts and tips. It is rebuilt each time the script loads, and the new !smarthelp command rebuilds it and whispers a link. Its body text inherits the journal's colour, with mid-tone accents and translucent tints, so it reads in both Roll20 light and dark mode. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
An AoE with neither --saveFormula nor --noSave silently did nothing when triggered: each target sent an empty roll that Roll20 never answered, so no card was posted, no damage was applied and no error was shown. Treat it as no save, so every target takes full damage, as --noSave|true does, and say so in the help handout. Found during live testing. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
For circles, squares and cones cast from a corner or face of a large token, the area searched for covered squares was shifted by the origin offset (subtracted at both ends), and the vertical offset and size were ignored, so squares along an edge of the AoE could be missed. Widen the search box by the size of the offset on each axis instead, which covers the whole shape without changing it, and correct the parameter documentation. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
A circle smaller than a grid square that sits inside it never crosses the square's edges, so no square was marked and the AoE was empty. Keep that square when the circle lies within it and covers at least --minGridArea of it. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
When several AoEs from the same caster were triggered at once, the damage rolls and targets were collected across all of them, so each card also showed the earlier AoEs' damage rows and targets. Collect them per AoE. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
!smartrotateorigin redrew every AoE linked to the selected token with the rotated AoE's offset. Redraw only the AoE being rotated, skip AoEs whose caster token has been deleted, and drop two unused variables. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
The --controlTokSide check used || where && was meant, so any number passed and a side that did not exist failed later. On an invalid side the function also returned an unrelated shared variable. Report the side requested and the number available, stop creating the AoE, quote the player's name in the whisper and let the caller stop cleanly. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
deleteLinkedPaths reused the shared "path" variable that AoE creation was still using, so deleting old squares could clear the path of the AoE being created. Use a local variable. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Roll results are arrays with one entry per inline roll, so a rule such as "+2" joined them as text ([5] + 2 gave "52") and a line with two rolls compared as NaN. Use the line's total for damage and its first roll for a save. SmartAoE passes one roll per line in normal use, so this is a safeguard. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
The !smartpinggm / !smartpingall pattern was a character class rather than "gm or all" and was not anchored to the start of the message. Match it properly, and stop after the first matching command so a message runs only one handler. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
- getAngle2ControlToken returns 0 for two identical points instead of NaN, and getNewControlPt handles angles outside 0-360 degrees instead of returning undefined. - getNearestOriginPt falls back to the control point if no edge crossing is found, instead of measuring from undefined. - toFullColor strips every non-hex character, not just the first. - checkInstall skips the collections macro with a log line when no GM is found, instead of throwing. - Use && where a bitwise & was meant, show every slash in the chat card's damage with spaces, make dTheta and oY local, remove an unused variable and correct the getWallParams comments. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Remove dTheta, i, layer, newSideImg, oY and tokenPic from the shared working variables, now that every use declares its own, and declare the remaining loop counter and token values locally. Correct the JSDoc for makeAoELink's path id. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
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 supplies only its inside test and edge crossings; - the 56 abilities of the AoEControlToken character come from one table, CONTROL_TOKEN_ABILITIES; - roll results for damage rolls and targets are formatted by one function, attachRollResults; - the chat commands find their caller with one getCaller helper; - 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 are removed, and processInlinerolls2 is renamed substituteInlineRolls; - clearCache only ever cleared everything, so it becomes 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; - unused parameters, variables and return values are removed. 28,500 random shape calculations give the same squares before and after, and the end-to-end scenarios give identical chat output, objects and state. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
README: features (including the help handout), installation, how it works, every chat command and !smartaoe parameter grouped by topic, example macros, integration with other scripts (including the script API and ZeroHPMarkers), a Jumpgate section with the layering workaround and a note on 2024 (Beacon) sheets, tips and known limitations collected from the Roll20 forums, development notes and credits. TESTING.md: a manual checklist for setup, the handout, creating and triggering every shape, damage and healing, markers and removal, Trigger-Target and stale links, calls from other scripts and error reporting. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
…json Add MidNiteShadow7 to the authors, mention the help handout in the description, and declare the objects and properties SmartAoE reads and writes at property level: tokens, paths, pages, the AoEControlToken character and its abilities, attributes, the AoEGenerator macro, the help handout, players, jukebox tracks and the campaign's player page, token markers and turn order. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Bump the version to 1.0.0 in the script and script.json, adding 0.30 to the previous versions, add the 1.0.0 version folder, and record the release in a new changelog: what was added, changed and fixed since 0.30, with links to the forum reports, plus the history of earlier versions. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Adds the release date for version 1.0.0 to the CHANGELOG.md. Also applies a minor formatting adjustment to the credits section in the README.md. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
…heets
SmartAoE only read character data as legacy attribute objects, through
findObjs and getAttrByName. The D&D 2024 (Beacon) sheet doesn't store
its values that way, so on a 2024 sheet:
- a{...} / @{...} references in --saveFormula, and the 5eSTR...5eCHA
shorthands, came out empty, so every save was a bare d20;
- --resistAttr, --vulnerableAttr and --immunityAttr were never found,
so targets took full damage whatever their resistances;
- --ignore never matched.
When no legacy attribute is found, these now read the sheet with
Roll20's getSheetItem, then try the custom-attribute form user.<name>.
A sheet item that exists but is empty stops the lookup there, because
Roll20 logs an error to the API console for every missing user.*
attribute. Beacon values are turned into text for roll formulas: true
and false become 1 and 0, and lists are joined with commas.
replaceAttributes and processTokenRolls are now async, and the trigger
waits for each target. 2014-sheet characters keep using their attribute
objects and getAttrByName defaults as before. Magic Resistance
detection still needs legacy attributes.
The 1.0.0 snapshot is updated to match.
Reported on the Roll20 forums:
https://app.roll20.net/forum/post/12842711/script-update-smartaoe/?pageforid=12843607#post-12843607
Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
--resource only looked for a legacy attribute object, so on a D&D 2024 (Beacon) sheet it always replied "Resource '...' is not found", even when the value existed on the sheet. A new readCharacterValue helper reads the legacy attribute if there is one and otherwise the sheet item. A Beacon resource is then spent with setSheetItem. If the sheet refuses the change, the AoE isn't created and the GM is told why, so a spell is never cast without spending its resource. 2014-sheet resources are still set on the attribute object. The 1.0.0 snapshot is updated to match. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
The D&D 2024 sheet's spell slot fields count slots used, not slots left: lvl3_slots_expended starts at 0, and the total is kept in lvl3_slots_total. --resource only subtracted, so with no slots used it always said there weren't enough. An optional fourth value, --resource|attrName, cost, alias, limit, makes the resource count up instead. The cost is added, and the AoE is blocked if that would take the value past the limit. The limit can be a number, "max" (the resource's own max) or another attribute, e.g. --resource|lvl3_slots_expended, 1, Level 3 slots, lvl3_slots_total Without a limit, --resource counts down as before. An empty alias now falls back to the attribute name, so "name, 1, , limit" works. If the limit is missing or not a number, the cast stops and the GM is told why. getSheetValue and readCharacterValue take a 'current' or 'max' type for the "max" limit, and the help handout's --resource row describes the new form. Tested in a live D&D 2024 game: a caster with three 1st-level slots went to 1, 2 and 3 of 3 used, and the fourth cast was blocked. The 1.0.0 snapshot is updated to match. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
README: - replace the 2024-sheet note, which advised a custom --saveFormula that could never have worked, with a "D&D 2024 (Beacon) sheets" section: what is read from the sheet, the need for Mod Script Sandbox v1.5, how Beacon resources are spent, that 2024 spell slots count up, and how to check an attribute name in chat; - describe the --resource limit in the parameter table, and note that Magic Resistance is only detected on 2014 sheets. CHANGELOG: add the --resource limit under Added, and the Beacon sheet fixes under Fixed with a link to the forum report. TESTING: add a D&D 2024 sheet section based on the reporter's Fireball macro (counting slots up to the total, being blocked at the limit, the save bonus and immunity, and a 2014-sheet comparison), and expect the 5eDEX save bonus on both sheets. Signed-off-by: Steve Roberts <steve@shadowcomputers.co.uk>
Alicekb
approved these changes
Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Releases SmartAoE v1.0.0, marking the script as stable with no breaking changes for existing macros or AoEs. This version includes a major code refactor, new features, numerous bug fixes, and support for the D&D 2024 (Beacon) character sheet, improving reliability and the user experience.
Refactoring and Code Quality
script.jsonto accurately reflect the script and its object access.README.md,CHANGELOG.mdandTESTING.mdfiles provide documentation (including a D&D 2024 sheet section) and a manual testing checklist.New Features
!smarthelpcommand.--resourceaccepts an optional limit:--resource|attrName, cost, alias, limit, so a resource can count up to a limit instead of down to 0. The limit can be a number,max(the resource's own max) or another attribute. The D&D 2024 sheet needs this for spell slots, which count slots used:--resource|lvl3_slots_expended, 1, Level 3 slots, lvl3_slots_total. Without a limit,--resourceworks as before.SmartAoE.ZeroHPMarkers()as a public API function, allowing other scripts to retrieve a list of 0 HP marker tags used by SmartAoE during the current session.Key Bug Fixes
getSheetItem, then tries the custom-attribute formuser.<name>. 2014-sheet games keep using attribute objects as before.a{…}/@{…}references in--saveFormula, and the5eSTR…5eCHAshorthands, now include the target's bonus instead of rolling a bare d20.--resistAttr,--vulnerableAttrand--immunityAttrare now found, so resistances, vulnerabilities and immunities apply.--resourceno longer reports "Resource '…' is not found". Beacon resources are spent withsetSheetItem. If the sheet refuses the change, the AoE isn't created, and the GM is told why.--ignorenow matches on Beacon sheets.!smarttrigger,!smartquery,!smartrotateorigin, or!smartremovecommands are used with no selection, an invalid selection, or an unknown ID, instead providing clear whispers.!smarttriggerto correctly activate the AoE when a specific control token ID is provided without a target.ReferenceErrorduring initial setup.!smartpinggmand!smartpingallcommands and ensures only one command executes per chat message.[5] + 2yielding52).--damageFormula2now works reliably with both plain numbers and inline rolls, independently of--damageFormula1, and displays correctly on the chat card even if--damageFormula1is absent or zero.(@Max)note for full healing appears as expected.--removeAtZerofunctionality is restored, correctly removing tokens when their last designated health bar reaches zero.--conditionPass,--conditionFail,--zeroHPmarker,--casterCondition, and chat-button markers) is now robust, preventing duplicate markers, accurately matching existing markers by tag, and avoiding extraneous commas. Observers are only notified when actual changes occur.distBetweenPtsfunction now correctly returns0for zero-length segments instead ofNaN, andapplyMathRuleleaves values unchanged for unknown operators.deleteLinkedPathsto prevent overwriting the sharedpathvariable during AoE creation.!smartrotateoriginnow redraws only the specific AoE being rotated, enhancing performance and preventing unintended changes to other AoEs.*,{,>,"from libInline's HTML encoding) were not correctly evaluated; now decoding these entities for accurate roll processing.--aoeColorand--aoeOutlineColorvalues provided by other API scripts are preserved, addressing a previous bug where they were overwritten by player colours.!smarttriggerin Jumpgate games now correctly identifies and triggers AoEs even when an AoE grid square is selected instead of the control token (due to layering issues).!smartqueryand!smarttriggerfailing with "Cannot read properties of undefined" when encountering stale AoE paths.--controlTokSidevalues, preventing crashes due to invalid side numbers.Testing
--resourcecounts down as before;5eDEXanda{…}save bonuses are correct for NPCs and PCs;