` pills) to appear in public chat instead of re-displaying the interactive GM-whispered menu. Root cause was `handleInput` calling `showSettingsInGameChat()` on every setting change instead of `showMenu()`. The interactive GM menu is now always shown after a change; the read-only public snapshot remains available via the explicit `!aura settings` command. ([#3](https://github.com/steverobertsuk/roll20-api-scripts/issues/3), [#4](https://github.com/steverobertsuk/roll20-api-scripts/issues/4))
+- Fixed `!aura perc` not refreshing tokens already on the map: changing the HP threshold now immediately re-evaluates all existing tokens via `menuForceUpdate()`, so tokens that were visible under the old threshold are correctly cleared (or revealed) without requiring a token move.
+- Fixed "No FX with name" GM whisper appearing repeatedly when a character's `USEBLOOD` attribute is set to a custom custfx name that no longer exists in the campaign (e.g. after a campaign reset or character import). The error message now identifies the character by name so the GM knows which `USEBLOOD` attribute to correct. The script also falls back to `-DefaultHurt` so a visual effect still plays instead of silently doing nothing. ([#2](https://github.com/steverobertsuk/roll20-api-scripts/issues/2))
+
+### Changed
+- Removed orphaned `applyDefaultAura` function — it was superseded by `clearAuras()` in the threshold fix but never deleted.
+- Removed unused `changedSetting` variable from `handleInput` — it was assigned in ~20 places but never read, as `showMenu()` was always called unconditionally at the end of the function.
+- Token refresh progress messages updated: sender changed from a hardcoded string to `SCRIPT_NAME`; text changed from `"Fixing N Tokens"` / `"Finished Fixing Tokens"` to `"Refreshing N Tokens"` / `"Finished Refreshing Tokens"`.
+- `handleInput` command dispatch refactored — 27 switch cases reduced to 5 dispatch-table lookups (`TOGGLES`, `STRINGS`, `FLOATS`, `SHAPES`, `HEXES`) plus a 13-case switch for commands with unique behaviour, removing ~100 lines of repetitive boilerplate.
+- `showSettingsInGameChat` refactored to reduce cognitive complexity: extracted `boolPill` and `namePill` module-level helpers (read-only counterparts to the existing `toggleBtn`/`nameBtn`), replacing 8 repeated inline ternary expressions and an inline `pickNameStyle` closure.
+- JSDoc corrections: removed stale `@param [update]` from `applyAuraAndDead` (parameter was removed in an earlier refactor); corrected `spawnDefaultFxById` description which falsely claimed to tighten particle profile settings; corrected `boolPill` description (background colour, not text colour); corrected `registerEventHandlers` listing `change:token` instead of `change:graphic`; documented the missing British/American colour-key variants on `FX_PARAM_DEFAULTS`; removed stale `pColor` return-type entry from `resolveTypeConfig`.
+
+---
+
## [2.1.0] – 2026-05-01
### Added
@@ -44,9 +65,8 @@ Major modernization and stability refactor of the entire script, consolidating p
- **JSDoc Documentation** — every function is now fully documented with parameter types, return values, and behavioral descriptions to improve maintainability.
- **Centralized Configuration** — all default state values, FX definitions, and internal parameters are now managed via unified constants.
- **FX Recovery Commands** — added `!aura reset-fx` (rebuild default heal/hurt custom FX objects) and `!aura reset-all` (restore all settings to defaults + rebuild default FX + force update).
-- **Public Settings Snapshot** — setting-changing `!aura` commands now post a read-only settings panel to game chat for table visibility.
+- **Public Settings Snapshot** — added `!aura settings` to post a read-only settings panel to game chat on demand.
- **Aura Detail Commands** — added `!aura a1shape`, `!aura a1tint`, `!aura a2size`, `!aura a2shape`, and `!aura a2tint` to adjust displayed Aura 1/Aura 2 detail values from chat.
-- **Settings Output Command** — added `!aura settings` to post the current settings snapshot to game chat on demand.
### Changed
- **`_` (Underscore) dependency** — no longer required.
@@ -68,11 +88,22 @@ Major modernization and stability refactor of the entire script, consolidating p
- `!aura bar` now validates `1|2|3`, whispers confirmation on change, and immediately runs a full sync to apply the new bar selection.
- Tokens with no `max` value on the configured bar now have aura/tint cleared, preventing stale health indicators.
- Configuration output now explicitly includes Aura 2 detail rows in both the GM menu and public settings snapshot.
-- Prevented duplicate settings output: setting-changing commands now publish a single game-chat snapshot instead of both GM and public panels.
+- Prevented duplicate settings output: setting-changing commands re-display the GM menu instead of posting to both GM and public chat.
- Aura 2 output values are now sourced from state-backed defaults (`Aura2Size`, `Aura2Shape`, `Aura2Color`) instead of hardcoded labels.
- Added Aura 1 Shape/Tint rows to settings output and backed them with default state values (`Aura1Shape`, `Aura1Color`).
- Default heal/hurt custom FX definitions are now synchronized proactively on install/reset and when FX colors change, preventing delayed/stale visual updates after color edits or token lifecycle events.
+---
+
+## [1.7.1] – 2025-02-16
+
+> **Note:** This version was sourced from a [Roll20 forum post](https://app.roll20.net/forum/permalink/12236299/) by [Surok](https://app.roll20.net/users/335573). It was never submitted to the Roll20 GitHub API scripts repository.
+
+### Added
+- Added `LISTFX` command/button that displays a graphical menu listing custom FX objects with their IDs.
+
+### Changed
+- Updated FX effects: damage/healing FX now use the simpler DeathTracker approach — damage uses the `HurtFX` type (default `splatter-blood`) and healing uses the `HealFX` type (default `glow-holy`).
---
diff --git a/HealthColors/HealthColors.js b/HealthColors/HealthColors.js
index 34afc8292..364a43ced 100644
--- a/HealthColors/HealthColors.js
+++ b/HealthColors/HealthColors.js
@@ -1,5 +1,5 @@
// ===========================
-// === HealthColors v2.1.0 ===
+// === HealthColors v2.1.1 ===
// ===========================
// AUTHORS:
@@ -8,14 +8,14 @@
/* global createObj TokenMod spawnFxWithDefinition spawnFx getObj state playerIsGM sendChat findObjs log on */
-(() => {
- "use strict";
+const HealthColors = (() => {
+ 'use strict';
// ————— CONSTANTS —————
- const VERSION = "2.1.0";
- const SCRIPT_NAME = "HealthColors";
- const SCHEMA_VERSION = "1.1.0";
- const UPDATED = "2026-04-25 07:30 UTC";
+ const VERSION = '2.1.1';
+ const SCRIPT_NAME = 'HealthColors';
+ const SCHEMA_VERSION = '1.1.0';
+ const UPDATED = '2026-05-07 16:00 UTC';
// ————— DEFAULTS —————
/**
@@ -51,7 +51,7 @@
*/
const DEFAULTS = {
auraColorOn: true,
- auraBar: "bar1",
+ auraBar: 'bar1',
auraTint: false,
auraPercPC: 100,
auraPerc: 100,
@@ -59,22 +59,22 @@
NPCAura: true,
auraDeadPC: true,
auraDead: true,
- GM_PCNames: "Yes",
- PCNames: "Yes",
- GM_NPCNames: "Yes",
- NPCNames: "Yes",
+ GM_PCNames: 'Yes',
+ PCNames: 'Yes',
+ GM_NPCNames: 'Yes',
+ NPCNames: 'Yes',
AuraSize: 0.35,
- Aura1Shape: "Circle",
- Aura1Color: "00FF00",
+ Aura1Shape: 'Circle',
+ Aura1Color: '00FF00',
Aura2Size: 5,
- Aura2Shape: "Square",
- Aura2Color: "806600",
+ Aura2Shape: 'Square',
+ Aura2Color: '806600',
OneOff: false,
FX: true,
- HealFX: "FDDC5C",
- HurtFX: "FF0000",
- auraDeadFX: "None",
- colorPalette: "default",
+ HealFX: 'FDDC5C',
+ HurtFX: 'FF0000',
+ auraDeadFX: 'None',
+ colorPalette: 'default',
};
const COLOR_PALETTES = {
@@ -187,8 +187,14 @@
* @property {number} angle - Fallback emission angle in degrees.
* @property {number} angleRandom - Fallback angular spread.
* @property {number} emissionRate - Fallback particles emitted per frame.
- * @property {number[]} startColor - Fallback RGBA start color (opaque white).
- * @property {number[]} endColor - Fallback RGBA end color (opaque black).
+ * @property {number[]} startColour - Fallback RGBA start colour (British spelling; opaque grey).
+ * @property {number[]} startColor - Fallback RGBA start color (American spelling; same value).
+ * @property {number[]} endColour - Fallback RGBA end colour (British spelling; opaque black).
+ * @property {number[]} endColor - Fallback RGBA end color (American spelling; same value).
+ * @property {number[]} startColourRandom - Fallback start colour randomization (zeroed).
+ * @property {number[]} startColorRandom - Fallback start color randomization (zeroed).
+ * @property {number[]} endColourRandom - Fallback end colour randomization (zeroed).
+ * @property {number[]} endColorRandom - Fallback end color randomization (zeroed).
* @property {{x:number,y:number}} gravity - Fallback gravity (none).
*/
const FX_PARAM_DEFAULTS = {
@@ -218,13 +224,14 @@
/**
* Converts a health percentage (0–100+) to a hex color using the active palette.
* Values above 100% return blue; 0% uses dead; 1–100 interpolate low→mid→high.
+ *
* @param {number} pct - Health percentage.
* @returns {string} A 6-digit hex color string, e.g. '#FF0000'.
*/
function percentToHex(pct) {
const normalizedPct = Math.max(0, Number(pct) || 0);
- if (normalizedPct > 100) return "#0000FF";
- const paletteName = state?.HealthColors?.colorPalette || "default";
+ if (normalizedPct > 100) return '#0000FF';
+ const paletteName = state?.HealthColors?.colorPalette || 'default';
const { high, mid, low, dead } =
COLOR_PALETTES[paletteName] || COLOR_PALETTES.default;
const rgbToHex = (rgb) =>
@@ -248,11 +255,12 @@
/**
* Parses a hex color string into an RGBA array suitable for Roll20 FX definitions.
* Returns [0,0,0,0] when the input is invalid.
+ *
* @param {string} hex - Hex color string with or without leading '#'.
* @returns {number[]} Array of [r, g, b, a] where a is always 1.0 on success.
*/
function hexToRgb(hex) {
- const cleanHex = (hex || "").replace("#", "").trim();
+ const cleanHex = (hex || '').replace('#', '').trim();
const parts = /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(
cleanHex,
);
@@ -269,68 +277,86 @@
/**
* Returns a random integer between min and max inclusive.
+ *
* @param {number} min - Lower bound (inclusive).
* @param {number} max - Upper bound (inclusive).
* @returns {number} Random integer in [min, max].
*/
function randomInt(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
+ return Math.floor(Math.random() * (max - min + 1)) + min; // NOSONAR — cosmetic FX variance, not security-sensitive
+ }
+
+ /**
+ * Creates a plain-object snapshot of a Roll20 API object or any serialisable value.
+ * Uses JSON round-trip rather than structuredClone so that Roll20 proxy objects have
+ * their toJSON() method called, producing a plain object whose properties are
+ * accessible directly (e.g. prev.bar1_value) rather than through .get().
+ *
+ * @param {object} obj - Roll20 API object or plain object to snapshot.
+ * @returns {object} Plain object deep copy.
+ */
+ function deepClone(obj) {
+ return JSON.parse(JSON.stringify(obj)); // NOSONAR — intentional: triggers Roll20 proxy toJSON()
}
/**
* Normalizes a 6-digit hex color string (without '#').
* Returns fallback when input is invalid.
+ *
* @param {string} value - Candidate hex string.
* @param {string} fallback - Fallback value when invalid.
* @returns {string} Uppercase 6-digit hex.
*/
function normalizeHex6(value, fallback) {
- const cleaned = (value || "").replace("#", "").trim().toUpperCase();
+ const cleaned = (value || '').replace('#', '').trim().toUpperCase();
return /^[0-9A-F]{6}$/.test(cleaned) ? cleaned : fallback;
}
/**
* Normalizes an aura shape label to supported display values.
+ *
* @param {string} value - Candidate shape value.
* @param {string} fallback - Fallback shape.
* @returns {string} One of Circle|Square.
*/
function normalizeShape(value, fallback) {
- const shape = (value || "").trim().toUpperCase();
- if (shape === "CIRCLE") return "Circle";
- if (shape === "SQUARE") return "Square";
+ const shape = (value || '').trim().toUpperCase();
+ if (shape === 'CIRCLE') return 'Circle';
+ if (shape === 'SQUARE') return 'Square';
return fallback;
}
/**
* Normalizes a palette name to one of the supported keys.
+ *
* @param {string} value - Candidate palette key.
* @param {string} fallback - Fallback palette key when invalid.
* @returns {string} A valid palette key from COLOR_PALETTES.
*/
function normalizePalette(value, fallback) {
- const p = (value || "").trim().toLowerCase();
+ const p = (value || '').trim().toLowerCase();
return COLOR_PALETTES[p] ? p : fallback;
}
// ————— WHISPER GM (declared early; used by checkInstall) —————
/**
* Sends a styled whisper message to the GM.
+ *
* @param {string} text - Plain text content to display inside the styled div.
*/
function gmWhisper(text) {
const style = [
- "width:100%",
- "border-radius:4px",
- "box-shadow:1px 1px 1px #707070",
- "text-align:center",
- "vertical-align:middle",
- "padding:3px 0px",
- "margin:0px auto",
- "border:1px solid #000",
- "color:#000",
- "background-image:-webkit-linear-gradient(-45deg,#a7c7dc 0%,#85b2d3 100%)",
- ].join(";");
+ 'width:100%',
+ 'border-radius:4px',
+ 'box-shadow:1px 1px 1px #707070',
+ 'text-align:center',
+ 'vertical-align:middle',
+ 'padding:3px 0px',
+ 'margin:0px auto',
+ 'border:1px solid #000',
+ 'color:#000',
+ 'background-image:-webkit-linear-gradient(-45deg,#a7c7dc 0%,#85b2d3 100%)',
+ ].join(';');
sendChat(SCRIPT_NAME, `/w GM ${text}
`);
}
@@ -339,6 +365,7 @@
* Creates a cached attribute lookup function that auto-refreshes on attribute
* change or destruction and re-triggers handleToken for affected tokens.
* Creates the attribute with the default value if it does not exist yet.
+ *
* @param {string} attribute - The Roll20 attribute name to track (e.g. 'USECOLOR').
* @param {object} [options={}] - Configuration options.
* @param {string} [options.default] - Value to use when the attribute is missing or invalid.
@@ -347,81 +374,62 @@
*/
function makeSmartAttrCache(attribute, options = {}) {
const cache = {};
- const defaultValue = options.default || "YES";
+ const defaultValue = options.default || 'YES';
const validator = options.validation || (() => true);
- on("change:attribute", (attr) => {
- if (attr.get("name") !== attribute) return;
- if (!validator(attr.get("current"))) attr.set("current", defaultValue);
- cache[attr.get("characterid")] = attr.get("current");
- findObjs({ type: "graphic" })
- .filter((o) => o.get("represents") === attr.get("characterid"))
+ on('change:attribute', (attr) => {
+ if (attr.get('name') !== attribute) return;
+ if (!validator(attr.get('current'))) attr.set('current', defaultValue);
+ cache[attr.get('characterid')] = attr.get('current');
+ findObjs({ type: 'graphic' })
+ .filter((o) => o.get('represents') === attr.get('characterid'))
.forEach((obj) => {
- const prev = JSON.parse(JSON.stringify(obj));
- handleToken(obj, prev, "YES");
+ const prev = deepClone(obj);
+ handleToken(obj, prev, 'YES');
});
});
- on("destroy:attribute", (attr) => {
- if (attr.get("name") === attribute) delete cache[attr.get("characterid")];
+ on('destroy:attribute', (attr) => {
+ if (attr.get('name') === attribute) delete cache[attr.get('characterid')];
});
return function (character) {
let attr =
findObjs(
- { type: "attribute", name: attribute, characterid: character.id },
+ { type: 'attribute', name: attribute, characterid: character.id },
{ caseInsensitive: true },
)[0] ||
- createObj("attribute", {
+ createObj('attribute', {
name: attribute,
characterid: character.id,
current: defaultValue,
});
- if (!cache[character.id] || cache[character.id] !== attr.get("current")) {
- if (!validator(attr.get("current"))) attr.set("current", defaultValue);
- cache[character.id] = attr.get("current");
+ if (!cache[character.id] || cache[character.id] !== attr.get('current')) {
+ if (!validator(attr.get('current'))) attr.set('current', defaultValue);
+ cache[character.id] = attr.get('current');
}
return cache[character.id];
};
}
- const lookupUseBlood = makeSmartAttrCache("USEBLOOD", { default: "DEFAULT" });
- const lookupUseColor = makeSmartAttrCache("USECOLOR", {
- default: "YES",
+ const lookupUseBlood = makeSmartAttrCache('USEBLOOD', { default: 'DEFAULT' });
+ const lookupUseColor = makeSmartAttrCache('USECOLOR', {
+ default: 'YES',
validation: (o) => Boolean(o.match(/YES|NO/)),
});
// ————— TOKEN HELPERS —————
- /**
- * Resets a token to the healthy/default visual state using the palette high color.
- * In tint mode it applies tint_color; otherwise it sets aura1 color/radius.
- * Roll20 measures aura1_radius from the token edge, so AuraSize maps directly.
- * @param {object} obj - Roll20 token graphic object.
- */
- function applyDefaultAura(obj) {
- const useTint = state.HealthColors.auraTint;
- if (useTint) {
- obj.set({ tint_color: percentToHex(100) });
- } else {
- obj.set({
- tint_color: "transparent",
- aura1_color: percentToHex(100),
- aura1_radius: state.HealthColors.AuraSize,
- showplayers_aura1: true,
- });
- }
- }
-
/**
* Hard-clears all health-indicator visual settings (aura/tint).
* Used for dead tokens or when the script/aura is disabled for a type.
+ *
* @param {object} obj - Roll20 token graphic object.
*/
function clearAuras(obj) {
obj.set({
- tint_color: "transparent",
- aura1_color: "transparent",
+ tint_color: 'transparent',
+ aura1_color: 'transparent',
aura1_radius: 0,
});
}
@@ -430,6 +438,7 @@
* Applies a health color to a token via aura or tint depending on configuration.
* When in tint mode, sets tint_color. When in aura mode, sets aura radius and color.
* Roll20 measures aura1_radius from the token edge, so sizeSet maps directly.
+ *
* @param {object} obj - Roll20 token object.
* @param {number} sizeSet - Feet the ring extends beyond the token edge (e.g. 0.35).
* @param {string} markerColor - Hex color string derived from health percentage.
@@ -440,7 +449,7 @@
obj.set({ tint_color: markerColor });
} else {
obj.set({
- tint_color: "transparent",
+ tint_color: 'transparent',
aura1_radius: sizeSet,
aura1_color: markerColor,
showplayers_aura1: true,
@@ -451,26 +460,28 @@
/**
* Sets token name-visibility flags for the GM and players.
* 'Yes' → true, 'No' → false, 'Off' → leave unchanged.
+ *
* @param {string} gm - GM name-display setting: 'Yes', 'No', or 'Off'.
* @param {string} pc - Player name-display setting: 'Yes', 'No', or 'Off'.
* @param {object} obj - Roll20 token object.
*/
function setShowNames(gm, pc, obj) {
- if (gm !== "Off" && gm !== "") obj.set({ showname: gm === "Yes" });
- if (pc !== "Off" && pc !== "") obj.set({ showplayers_name: pc === "Yes" });
+ if (gm !== 'Off' && gm !== '') obj.set({ showname: gm === 'Yes' });
+ if (pc !== 'Off' && pc !== '') obj.set({ showplayers_name: pc === 'Yes' });
}
// ————— FX —————
/**
* Plays a jukebox track when a token dies.
* Accepts a comma-separated list of track names; picks one at random.
+ *
* @param {string} trackname - Track name or comma-separated list of track names.
*/
function playDeath(trackname) {
const list =
- trackname.indexOf(",") > 0 ? trackname.split(",") : [trackname];
- const resolvedName = list[Math.floor(Math.random() * list.length)];
- const track = findObjs({ type: "jukeboxtrack", title: resolvedName })[0];
+ trackname.indexOf(',') > 0 ? trackname.split(',') : [trackname];
+ const resolvedName = list[Math.floor(Math.random() * list.length)]; // NOSONAR — random track selection, not security-sensitive
+ const track = findObjs({ type: 'jukeboxtrack', title: resolvedName })[0];
if (track) {
track.set({ playing: false, softstop: false, volume: 50 });
track.set({ playing: true });
@@ -482,6 +493,7 @@
/**
* Spawns a scaled particle FX at a token's position using a custom FX definition.
* Merges the provided definition against FX_PARAM_DEFAULTS so partial definitions work.
+ *
* @param {number} scale - Scaling factor derived from token height (height / 70).
* @param {number} hitSize - Hit-size factor based on damage proportion (0.2–1.0).
* @param {number} left - Horizontal pixel position of the token on the page.
@@ -505,23 +517,23 @@
return undefined;
};
const startKeys = [
- "startColour",
- "startColor",
- "startcolour",
- "startcolor",
+ 'startColour',
+ 'startColor',
+ 'startcolour',
+ 'startcolor',
];
- const endKeys = ["endColour", "endColor", "endcolour", "endcolor"];
+ const endKeys = ['endColour', 'endColor', 'endcolour', 'endcolor'];
const startRndKeys = [
- "startColourRandom",
- "startColorRandom",
- "startcolourrandom",
- "startcolorrandom",
+ 'startColourRandom',
+ 'startColorRandom',
+ 'startcolourrandom',
+ 'startcolorrandom',
];
const endRndKeys = [
- "endColourRandom",
- "endColorRandom",
- "endcolourrandom",
- "endcolorrandom",
+ 'endColourRandom',
+ 'endColorRandom',
+ 'endcolourrandom',
+ 'endcolorrandom',
];
const startClr = pick(fx, startKeys) ?? pick(m, startKeys);
const endClr = pick(fx, endKeys) ?? pick(m, endKeys);
@@ -560,16 +572,17 @@
/**
* Safely reads a Roll20 custfx definition and returns a plain mutable object.
* Roll20 may return the definition as either an object or a JSON string.
+ *
* @param {object} fxObj - Roll20 custfx object.
* @returns {object|null} Parsed FX definition object, or null if unavailable/invalid.
*/
function getFxDefinition(fxObj) {
if (!fxObj) return null;
- const raw = fxObj.get("definition");
+ const raw = fxObj.get('definition');
if (!raw) return null;
- if (typeof raw === "string") {
+ if (typeof raw === 'string') {
try {
return JSON.parse(raw);
} catch (err) {
@@ -578,8 +591,8 @@
}
}
- if (typeof raw === "object") {
- return JSON.parse(JSON.stringify(raw));
+ if (typeof raw === 'object') {
+ return deepClone(raw);
}
return null;
@@ -606,28 +619,28 @@
state.HealthColors.colorPalette,
DEFAULTS.colorPalette,
);
- if (typeof TokenMod !== "undefined" && TokenMod.ObserveTokenChange) {
+ if (typeof TokenMod !== 'undefined' && TokenMod.ObserveTokenChange) {
TokenMod.ObserveTokenChange(handleToken);
}
const fxHurt = findObjs(
- { _type: "custfx", name: "-DefaultHurt" },
+ { _type: 'custfx', name: '-DefaultHurt' },
{ caseInsensitive: true },
)[0];
const fxHeal = findObjs(
- { _type: "custfx", name: "-DefaultHeal" },
+ { _type: 'custfx', name: '-DefaultHeal' },
{ caseInsensitive: true },
)[0];
if (!fxHurt) {
- gmWhisper("Creating Default Hurt FX");
- createObj("custfx", {
- name: "-DefaultHurt",
+ gmWhisper('Creating Default Hurt FX');
+ createObj('custfx', {
+ name: '-DefaultHurt',
definition: DEFAULT_HURT_FX,
});
}
if (!fxHeal) {
- gmWhisper("Creating Default Heal FX");
- createObj("custfx", {
- name: "-DefaultHeal",
+ gmWhisper('Creating Default Heal FX');
+ createObj('custfx', {
+ name: '-DefaultHeal',
definition: DEFAULT_HEAL_FX,
});
}
@@ -637,6 +650,7 @@
/**
* Builds the normalized default Hurt/Heal definition payload used for
* campaign custom FX objects.
+ *
* @param {boolean} isHeal - True for Heal profile, false for Hurt profile.
* @param {object} baseDef - Existing definition to merge into.
* @returns {object} Updated definition with normalized color/profile fields.
@@ -685,11 +699,11 @@
*/
function syncDefaultFxObjects() {
const fxHurt = findObjs(
- { _type: "custfx", name: "-DefaultHurt" },
+ { _type: 'custfx', name: '-DefaultHurt' },
{ caseInsensitive: true },
)[0];
const fxHeal = findObjs(
- { _type: "custfx", name: "-DefaultHeal" },
+ { _type: 'custfx', name: '-DefaultHeal' },
{ caseInsensitive: true },
)[0];
if (fxHeal) {
@@ -708,11 +722,11 @@
*/
function resetDefaultFxObjects() {
const existing = findObjs(
- { _type: "custfx" },
+ { _type: 'custfx' },
{ caseInsensitive: true },
- ).filter((fx) => /-Default(Hurt|Heal)/i.test(fx.get("name") || ""));
+ ).filter((fx) => /-Default(Hurt|Heal)/i.test(fx.get('name') || ''));
existing.forEach((fx) => fx.remove());
- gmWhisper("Recreating Default Hurt/Heal FX");
+ gmWhisper('Recreating Default Hurt/Heal FX');
checkInstall();
}
@@ -733,7 +747,7 @@
*/
function runResetAllFlow() {
resetAllSettingsToDefaults();
- gmWhisper("RESET ALL: defaults restored + default FX + force update");
+ gmWhisper('RESET ALL: defaults restored + default FX + force update');
resetDefaultFxObjects();
menuForceUpdate();
}
@@ -743,6 +757,7 @@
* Reads the configured health bar from a token and its previous snapshot,
* validates all three values are numeric, and returns a health data object.
* Returns null if any value is missing or non-numeric.
+ *
* @param {object} obj - Roll20 token graphic object.
* @param {object} prev - Snapshot of the token's previous attribute values.
* @param {string} [update] - Pass 'YES' when called from a forced refresh.
@@ -751,13 +766,13 @@
*/
function getBarHealth(obj, prev, update) {
const barUsed = state.HealthColors.auraBar;
- if (obj.get(`${barUsed}_max`) === "" && obj.get(`${barUsed}_value`) === "")
+ if (obj.get(`${barUsed}_max`) === '' && obj.get(`${barUsed}_value`) === '')
return null;
const maxValue = Number.parseInt(obj.get(`${barUsed}_max`), 10);
const curValue = Number.parseInt(obj.get(`${barUsed}_value`), 10);
const prevValue = prev[`${barUsed}_value`];
if (Number.isNaN(maxValue) || Number.isNaN(curValue)) return null;
- if (update !== "YES" && Number.isNaN(Number.parseInt(prevValue, 10)))
+ if (update !== 'YES' && Number.isNaN(Number.parseInt(prevValue, 10)))
return null;
const percReal = Math.max(
0,
@@ -769,12 +784,13 @@
/**
* Determines Player vs Monster and returns all type-specific config in one object.
+ *
* @param {object|undefined} oCharacter - Roll20 character object (may be undefined).
* @returns {{ gm: string, pc: string, isTypeOn: boolean, percentOn: number,
- * showDead: boolean, pColor: string }}
+ * showDead: boolean }}
*/
function resolveTypeConfig(oCharacter) {
- const isPlayer = oCharacter && oCharacter.get("controlledby") !== "";
+ const isPlayer = oCharacter && oCharacter.get('controlledby') !== '';
if (isPlayer) {
return {
gm: state.HealthColors.GM_PCNames,
@@ -796,43 +812,46 @@
/**
* Manages the dead-status marker and plays a death sound when a token reaches 0 HP.
* Extracted from applyAuraAndDead to reduce nesting depth.
+ *
* @param {object} obj - Roll20 token graphic object.
* @param {number} curValue - Current bar value.
* @param {number} prevValue - Previous bar value (may be a string).
*/
function applyDeadStatus(obj, curValue, prevValue) {
if (curValue > 0) {
- obj.set("status_dead", false);
+ obj.set('status_dead', false);
return;
}
const deadSfx = state.HealthColors.auraDeadFX;
- if (deadSfx !== "None" && curValue !== Number(prevValue))
+ if (deadSfx !== 'None' && curValue !== Number(prevValue))
playDeath(deadSfx);
- obj.set("status_dead", true);
+ obj.set('status_dead', true);
}
/**
* Applies or removes the health aura/tint and manages the dead-status marker.
+ *
* @param {object} obj - Roll20 token graphic object.
* @param {object|undefined} oCharacter - Roll20 character object.
* @param {object} typeConfig - Config returned by resolveTypeConfig.
* @param {object} health - Health data returned by getBarHealth.
- * @param {string} [update] - Pass 'YES' to indicate a forced refresh.
*/
function applyAuraAndDead(obj, oCharacter, typeConfig, health) {
const { curValue, prevValue, percReal, markerColor } = health;
const { isTypeOn, percentOn, showDead } = typeConfig;
const useAura = oCharacter ? lookupUseColor(oCharacter) : undefined;
const useTint = state.HealthColors.auraTint;
- const colorType = useTint ? "tint" : "aura1";
+ const colorType = useTint ? 'tint' : 'aura1';
if (showDead) applyDeadStatus(obj, curValue, prevValue);
- if (isTypeOn && useAura !== "NO") {
- if (curValue === 0) {
+ if (isTypeOn && useAura !== 'NO') {
+ if (curValue <= 0) {
tokenSet(obj, state.HealthColors.AuraSize, markerColor);
- } else if (percReal >= percentOn) {
- applyDefaultAura(obj);
+ } else if (percentOn <= 0) {
+ clearAuras(obj);
+ } else if (percReal > percentOn) {
+ clearAuras(obj);
} else {
tokenSet(obj, state.HealthColors.AuraSize, markerColor);
}
@@ -843,16 +862,18 @@
/**
* Builds the list of FX definition objects to spawn for a heal or hurt event.
+ *
* @param {boolean} isHeal - True when HP went up.
* @param {string|undefined} useBlood - Per-character blood FX override value.
+ * @param {string} [label] - Character/token name for error context.
* @returns {object[]} Array of Roll20 custfx definition objects.
*/
- function buildFXList(isHeal, useBlood) {
+ function buildFXList(isHeal, useBlood, label) {
const fxArray = [];
if (isHeal) {
const aFX = findObjs(
- { _type: "custfx", name: "-DefaultHeal" },
+ { _type: 'custfx', name: '-DefaultHeal' },
{ caseInsensitive: true },
)[0];
const def = getFxDefinition(aFX);
@@ -874,14 +895,14 @@
}
const aFX = findObjs(
- { _type: "custfx", name: "-DefaultHurt" },
+ { _type: 'custfx', name: '-DefaultHurt' },
{ caseInsensitive: true },
)[0];
const def = getFxDefinition(aFX);
if (!def) return fxArray;
- if (useBlood === "DEFAULT" || useBlood === undefined) {
+ if (useBlood === 'DEFAULT' || useBlood === undefined) {
const hurtRgb = hexToRgb(state.HealthColors.HurtFX);
def.startColour = hurtRgb;
def.startColor = hurtRgb;
@@ -906,9 +927,9 @@
def.endColorRandom = [0, 0, 0, 0];
fxArray.push(def);
} else {
- useBlood.split(",").forEach((fxName) => {
+ useBlood.split(',').forEach((fxName) => {
const custom = findObjs(
- { _type: "custfx", name: fxName.trim() },
+ { _type: 'custfx', name: fxName.trim() },
{ caseInsensitive: true },
)[0];
const customDef = getFxDefinition(custom);
@@ -916,7 +937,15 @@
if (customDef) {
fxArray.push(customDef);
} else {
- gmWhisper(`No FX with name ${fxName}`);
+ const who = label ? ` (character: "${label}")` : '';
+ log(`${SCRIPT_NAME}: Custom FX "${fxName.trim()}"${who} not found — check the USEBLOOD attribute.`);
+ gmWhisper(`Custom FX "${fxName.trim()}"${who} not found. Fix the USEBLOOD attribute on that character. Falling back to default hurt FX.`);
+ const fallbackFx = findObjs(
+ { _type: 'custfx', name: '-DefaultHurt' },
+ { caseInsensitive: true },
+ )[0];
+ const fallbackDef = getFxDefinition(fallbackFx);
+ if (fallbackDef) fxArray.push(fallbackDef);
}
});
}
@@ -926,30 +955,32 @@
}
/**
- * Workaround path: update default custfx definitions, then spawn by saved FX id.
- * This avoids client-side issues seen in some sandboxes with spawnFxWithDefinition.
- * Applies only to DEFAULT heal/hurt colors; custom named FX still use definition spawn.
- * Also tightens particle profile settings to keep color visibility consistent.
- * @param {object} obj - Roll20 token graphic object.
- * @param {boolean} isHeal - True when HP increased.
- * @param {string|undefined} useBlood - Per-character blood override.
- * @returns {boolean} True when the fallback path handled spawning.
+ * Spawns the default heal or hurt FX by their saved custfx ID using spawnFx.
+ * This avoids client-side color inconsistencies seen in some sandboxes when using
+ * spawnFxWithDefinition directly. Only handles DEFAULT heal/hurt colors; custom
+ * named FX (USEBLOOD set to a custfx name) still use the definition-spawn path.
+ *
+ * @param {object} obj - Roll20 token graphic object.
+ * @param {boolean} isHeal - True when HP increased.
+ * @param {string|undefined} useBlood - Per-character blood override value.
+ * @returns {boolean} True when spawning was handled; false if the caller should fall back.
*/
function spawnDefaultFxById(obj, isHeal, useBlood) {
- if (!(useBlood === "DEFAULT" || useBlood === undefined)) return false;
- const fxName = isHeal ? "-DefaultHeal" : "-DefaultHurt";
+ if (!(useBlood === 'DEFAULT' || useBlood === undefined)) return false;
+ const fxName = isHeal ? '-DefaultHeal' : '-DefaultHurt';
const aFX = findObjs(
- { _type: "custfx", name: fxName },
+ { _type: 'custfx', name: fxName },
{ caseInsensitive: true },
)[0];
if (!aFX) return false;
- spawnFx(obj.get("left"), obj.get("top"), aFX.id, obj.get("pageid"));
+ spawnFx(obj.get('left'), obj.get('top'), aFX.id, obj.get('pageid'));
return true;
}
/**
* Gates and triggers particle FX when HP changes on a non-forced update.
+ *
* @param {object} obj - Roll20 token graphic object.
* @param {object|undefined} oCharacter - Roll20 character object.
* @param {number} curValue - Current bar value.
@@ -965,26 +996,27 @@
maxValue,
update,
) {
- if (curValue === Number(prevValue) || prevValue === "" || update === "YES")
+ if (curValue === Number(prevValue) || prevValue === '' || update === 'YES')
return;
const useBlood = oCharacter ? lookupUseBlood(oCharacter) : undefined;
- if (!state.HealthColors.FX || useBlood === "OFF" || useBlood === "NO")
+ if (!state.HealthColors.FX || useBlood === 'OFF' || useBlood === 'NO')
return;
const isHeal = curValue > Number(prevValue);
const amount = Math.abs(curValue - Number(prevValue));
- const scale = obj.get("height") / 70;
+ const scale = obj.get('height') / 70;
const hitSize =
Math.max(Math.min((amount / maxValue) * 4, 1), 0.2) *
(randomInt(60, 100) / 100);
+ const fxLabel = (oCharacter && oCharacter.get('name')) || obj.get('name') || '';
if (spawnDefaultFxById(obj, isHeal, useBlood)) return;
- buildFXList(isHeal, useBlood).forEach((fx) =>
+ buildFXList(isHeal, useBlood, fxLabel).forEach((fx) =>
spawnFX(
scale,
hitSize,
- obj.get("left"),
- obj.get("top"),
+ obj.get('left'),
+ obj.get('top'),
fx,
- obj.get("pageid"),
+ obj.get('pageid'),
),
);
}
@@ -994,6 +1026,7 @@
* Delegates to specialized helpers for health reading, type resolution,
* aura management, and FX spawning.
* Clears aura/tint when the selected health bar has no max value.
+ *
* @param {object} obj - The Roll20 token graphic object.
* @param {object} prev - Snapshot of the token's previous attribute values.
* @param {string} [update] - Pass 'YES' to indicate a forced refresh (suppresses FX).
@@ -1005,13 +1038,13 @@
}
if (
state.HealthColors.auraColorOn !== true ||
- obj.get("layer") !== "objects"
+ obj.get('layer') !== 'objects'
)
return;
- if (obj.get("represents") === "" && state.HealthColors.OneOff !== true)
+ if (obj.get('represents') === '' && state.HealthColors.OneOff !== true)
return;
const barUsed = state.HealthColors.auraBar;
- if (obj.get(`${barUsed}_max`) === "") {
+ if (obj.get(`${barUsed}_max`) === '') {
clearAuras(obj);
return;
}
@@ -1021,14 +1054,14 @@
const { maxValue, curValue, prevValue } = health;
const sizeChanged =
- prev.width !== obj.get("width") || prev.height !== obj.get("height");
+ prev.width !== obj.get('width') || prev.height !== obj.get('height');
// Only proceed if health changed, token was resized, or this is a forced update.
// The size check ensures aura is re-applied when a token is resized, even without an HP change.
- if (curValue === Number(prevValue) && update !== "YES" && !sizeChanged)
+ if (curValue === Number(prevValue) && update !== 'YES' && !sizeChanged)
return;
- const oCharacter = getObj("character", obj.get("represents"));
+ const oCharacter = getObj('character', obj.get('represents'));
const typeConfig = resolveTypeConfig(oCharacter);
applyAuraAndDead(obj, oCharacter, typeConfig, health);
@@ -1044,19 +1077,19 @@
*/
function menuForceUpdate() {
const workQueue = findObjs({
- type: "graphic",
- subtype: "token",
- layer: "objects",
+ type: 'graphic',
+ subtype: 'token',
+ layer: 'objects',
});
- sendChat("Fixing Tokens", `/w gm Fixing ${workQueue.length} Tokens`);
+ sendChat(SCRIPT_NAME, `/w gm Refreshing ${workQueue.length} Tokens`);
const drainQueue = () => {
const token = workQueue.shift();
if (token) {
- const prev = JSON.parse(JSON.stringify(token));
- handleToken(token, prev, "YES");
+ const prev = deepClone(token);
+ handleToken(token, prev, 'YES');
setTimeout(drainQueue, 0);
} else {
- sendChat("Fixing Tokens", "/w gm Finished Fixing Tokens");
+ sendChat(SCRIPT_NAME, '/w gm Finished Refreshing Tokens');
}
};
drainQueue();
@@ -1065,92 +1098,119 @@
/**
* Forces a health-color update on all currently selected tokens.
* Whispers the list of updated token names to the GM.
+ *
* @param {object} msg - Roll20 chat message object with a populated `selected` array.
*/
function manUpdate(msg) {
const allNames = msg.selected.reduce((acc, obj) => {
- const token = getObj("graphic", obj._id);
- const prev = JSON.parse(JSON.stringify(token));
- handleToken(token, prev, "YES");
- return `${acc}${token.get("name")}
`;
- }, "");
+ const token = getObj('graphic', obj._id);
+ const prev = deepClone(token);
+ handleToken(token, prev, 'YES');
+ return `${acc}${token.get('name')}
`;
+ }, '');
gmWhisper(allNames);
}
// ————— MENU —————
/**
* Builds a styled Roll20 chat button anchor element.
+ *
* @param {string} label - Button label text.
* @param {string} href - Roll20 API command (e.g. '!aura on').
* @param {string} [extraStyle=''] - Additional inline CSS to append to the base style.
* @returns {string} An HTML anchor string ready for sendChat.
*/
- function makeBtn(label, href, extraStyle = "") {
+ function makeBtn(label, href, extraStyle = '') {
const base = [
- "padding-top:1px",
- "text-align:center",
- "font-size:9pt",
- "width:48px",
- "height:14px",
- "border:1px solid black",
- "margin:1px",
- "background-color:#6FAEC7",
- "border-radius:4px",
- "box-shadow:1px 1px 1px #707070",
- ].join(";");
+ 'padding-top:1px',
+ 'text-align:center',
+ 'font-size:9pt',
+ 'width:48px',
+ 'height:14px',
+ 'border:1px solid black',
+ 'margin:1px',
+ 'background-color:#6FAEC7',
+ 'border-radius:4px',
+ 'box-shadow:1px 1px 1px #707070',
+ ].join(';');
return `${label}`;
}
/**
* Builds a non-interactive styled value pill for read-only output panels.
+ *
* @param {string} label - Display text.
* @param {string} [extraStyle=''] - Additional inline CSS to append to base style.
* @returns {string} A styled span element.
*/
- function makePill(label, extraStyle = "") {
+ function makePill(label, extraStyle = '') {
const base = [
- "display:inline-block",
- "padding-top:1px",
- "text-align:center",
- "font-size:9pt",
- "min-width:48px",
- "height:14px",
- "border:1px solid black",
- "margin:1px",
- "background-color:#6FAEC7",
- "border-radius:4px",
- "box-shadow:1px 1px 1px #707070",
- "line-height:14px",
- "padding-left:4px",
- "padding-right:4px",
- ].join(";");
+ 'display:inline-block',
+ 'padding-top:1px',
+ 'text-align:center',
+ 'font-size:9pt',
+ 'min-width:48px',
+ 'height:14px',
+ 'border:1px solid black',
+ 'margin:1px',
+ 'background-color:#6FAEC7',
+ 'border-radius:4px',
+ 'box-shadow:1px 1px 1px #707070',
+ 'line-height:14px',
+ 'padding-left:4px',
+ 'padding-right:4px',
+ ].join(';');
return `${label}`;
}
/**
* Builds a toggle-style button that shows red when the value is false/off.
+ *
* @param {boolean} value - Current boolean state (true = on/green, false = off/red).
* @param {string} href - Roll20 API command to execute on click.
* @returns {string} An HTML anchor string.
*/
function toggleBtn(value, href) {
- const style = value === true ? "" : "background-color:#A84D4D";
- return makeBtn(value === true ? "Yes" : "No", href, style);
+ const style = value === true ? '' : 'background-color:#A84D4D';
+ return makeBtn(value === true ? 'Yes' : 'No', href, style);
}
/**
* Builds a three-state name-setting button. Red for 'No', grey for 'Off', default for 'Yes'.
+ *
* @param {string} value - Current value: 'Yes', 'No', or 'Off'.
* @param {string} href - Roll20 API command to execute on click.
* @returns {string} An HTML anchor string.
*/
function nameBtn(value, href) {
- let style = "";
- if (value === "No") style = "background-color:#A84D4D";
- if (value === "Off") style = "background-color:#D6D6D6";
+ let style = '';
+ if (value === 'No') style = 'background-color:#A84D4D';
+ if (value === 'Off') style = 'background-color:#D6D6D6';
return makeBtn(value, href, style);
}
+ /**
+ * Read-only pill counterpart to toggleBtn: green background for true, red for false.
+ * @param {boolean} value - Current boolean state.
+ * @returns {string} A styled span element.
+ */
+ function boolPill(value) {
+ return makePill(value ? 'Yes' : 'No', value ? '' : 'background-color:#A84D4D');
+ }
+
+ /**
+ * Read-only pill counterpart to nameBtn: red for 'No', grey for 'Off', default for 'Yes'.
+ *
+ * @param {string} value - Current value: 'Yes', 'No', or 'Off'.
+ * @returns {string} A styled span element.
+ */
+ function namePill(value) {
+ let style = '';
+ if (value === 'No') style = 'background-color:#A84D4D';
+ if (value === 'Off') style = 'background-color:#D6D6D6';
+ return makePill(value, style);
+ }
+
/**
* Renders and whispers the HealthColors configuration menu to the GM.
* Builds the full HTML panel using makeBtn/toggleBtn/nameBtn helpers and
@@ -1160,17 +1220,17 @@
const s = state.HealthColors;
const hr = `
`;
const wrapStyle = [
- "border-radius:8px",
- "padding:5px",
- "font-size:9pt",
- "text-shadow:-1px -1px #222,1px -1px #222,-1px 1px #222,1px 1px #222,2px 2px #222",
- "box-shadow:3px 3px 1px #707070",
- "background-image:-webkit-linear-gradient(left,#76ADD6 0%,#a7c7dc 100%)",
- "color:#FFF",
- "border:2px solid black",
- "text-align:right",
- "vertical-align:middle",
- ].join(";");
+ 'border-radius:8px',
+ 'padding:5px',
+ 'font-size:9pt',
+ 'text-shadow:-1px -1px #222,1px -1px #222,-1px 1px #222,1px 1px #222,2px 2px #222',
+ 'box-shadow:3px 3px 1px #707070',
+ 'background-image:-webkit-linear-gradient(left,#76ADD6 0%,#a7c7dc 100%)',
+ 'color:#FFF',
+ 'border:2px solid black',
+ 'text-align:right',
+ 'vertical-align:middle',
+ ].join(';');
const percLabel = `${s.auraPercPC}/${s.auraPerc}`;
const healBtnStyle = `background-color:#${s.HealFX}`;
@@ -1182,93 +1242,86 @@
``,
`HealthColors Version: ${VERSION}
`,
hr,
- `Is On: ${toggleBtn(s.auraColorOn, "!aura on")}
`,
- `Health Bar: ${makeBtn(s.auraBar, "!aura bar ?{Bar|1|2|3}")}
`,
- `Use Tint: ${toggleBtn(s.auraTint, "!aura tint")}
`,
- `Palette: ${makeBtn(s.colorPalette, "!aura palette ?{Palette|default|colorblind}", "width:80px")} (auto refreshes all tokens)
`,
- `Percentage(PC/NPC): ${makeBtn(percLabel, "!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}")}
`,
+ `Is On: ${toggleBtn(s.auraColorOn, '!aura on')}
`,
+ `Health Bar: ${makeBtn(s.auraBar, '!aura bar ?{Bar|1|2|3}')}
`,
+ `Use Tint: ${toggleBtn(s.auraTint, '!aura tint')}
`,
+ `Palette: ${makeBtn(s.colorPalette, '!aura palette ?{Palette|default|colorblind}', 'width:80px')} (auto refreshes all tokens)
`,
+ `Percentage(PC/NPC): ${makeBtn(percLabel, '!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}')}
`,
hr,
- `Show PC Health: ${toggleBtn(s.PCAura, "!aura pc")}
`,
- `Show NPC Health: ${toggleBtn(s.NPCAura, "!aura npc")}
`,
- `Show Dead PC: ${toggleBtn(s.auraDeadPC, "!aura deadPC")}
`,
- `Show Dead NPC: ${toggleBtn(s.auraDead, "!aura dead")}
`,
+ `Show PC Health: ${toggleBtn(s.PCAura, '!aura pc')}
`,
+ `Show NPC Health: ${toggleBtn(s.NPCAura, '!aura npc')}
`,
+ `Show Dead PC: ${toggleBtn(s.auraDeadPC, '!aura deadPC')}
`,
+ `Show Dead NPC: ${toggleBtn(s.auraDead, '!aura dead')}
`,
hr,
- `GM Sees all PC Names: ${nameBtn(s.GM_PCNames, "!aura gmpc ?{Setting|Yes|No|Off}")}
`,
- `GM Sees all NPC Names: ${nameBtn(s.GM_NPCNames, "!aura gmnpc ?{Setting|Yes|No|Off}")}
`,
+ `GM Sees all PC Names: ${nameBtn(s.GM_PCNames, '!aura gmpc ?{Setting|Yes|No|Off}')}
`,
+ `GM Sees all NPC Names: ${nameBtn(s.GM_NPCNames, '!aura gmnpc ?{Setting|Yes|No|Off}')}
`,
hr,
- `PC Sees all PC Names: ${nameBtn(s.PCNames, "!aura pcpc ?{Setting|Yes|No|Off}")}
`,
- `PC Sees all NPC Names: ${nameBtn(s.NPCNames, "!aura pcnpc ?{Setting|Yes|No|Off}")}
`,
+ `PC Sees all PC Names: ${nameBtn(s.PCNames, '!aura pcpc ?{Setting|Yes|No|Off}')}
`,
+ `PC Sees all NPC Names: ${nameBtn(s.NPCNames, '!aura pcnpc ?{Setting|Yes|No|Off}')}
`,
hr,
- `Aura 1 Radius (ft): ${makeBtn(s.AuraSize, "!aura size ?{Size?|0.35}")}
`,
- `Aura 1 Shape: ${makeBtn(s.Aura1Shape, "!aura a1shape ?{Shape?|Circle|Square}")}
`,
- `Aura 1 Color: ${makeBtn(s.Aura1Color, "!aura a1tint ?{Color?|00FF00}", aura1Style)}
`,
- `Aura 2 Radius (ft): ${makeBtn(String(s.Aura2Size), "!aura a2size ?{Size?|5}")}
`,
- `Aura 2 Shape: ${makeBtn(s.Aura2Shape, "!aura a2shape ?{Shape?|Square|Circle}")}
`,
- `Aura 2 Color: ${makeBtn(s.Aura2Color, "!aura a2tint ?{Color?|806600}", aura2Style)}
`,
- `One Offs: ${toggleBtn(s.OneOff, "!aura ONEOFF")}
`,
- `FX: ${toggleBtn(s.FX, "!aura FX")}
`,
- `HealFX Color: ${makeBtn(s.HealFX, "!aura HEAL ?{Color?|FDDC5C}", healBtnStyle)}
`,
- `HurtFX Color: ${makeBtn(s.HurtFX, "!aura HURT ?{Color?|FF0000}", hurtBtnStyle)}
`,
+ `Aura 1 Radius (ft): ${makeBtn(s.AuraSize, '!aura size ?{Size?|0.35}')}
`,
+ `Aura 1 Shape: ${makeBtn(s.Aura1Shape, '!aura a1shape ?{Shape?|Circle|Square}')}
`,
+ `Aura 1 Color: ${makeBtn(s.Aura1Color, '!aura a1tint ?{Color?|00FF00}', aura1Style)}
`,
+ `Aura 2 Radius (ft): ${makeBtn(String(s.Aura2Size), '!aura a2size ?{Size?|5}')}
`,
+ `Aura 2 Shape: ${makeBtn(s.Aura2Shape, '!aura a2shape ?{Shape?|Square|Circle}')}
`,
+ `Aura 2 Color: ${makeBtn(s.Aura2Color, '!aura a2tint ?{Color?|806600}', aura2Style)}
`,
+ `One Offs: ${toggleBtn(s.OneOff, '!aura ONEOFF')}
`,
+ `FX: ${toggleBtn(s.FX, '!aura FX')}
`,
+ `HealFX Color: ${makeBtn(s.HealFX, '!aura HEAL ?{Color?|FDDC5C}', healBtnStyle)}
`,
+ `HurtFX Color: ${makeBtn(s.HurtFX, '!aura HURT ?{Color?|FF0000}', hurtBtnStyle)}
`,
`DeathSFX: ${makeBtn(s.auraDeadFX.substring(0, 4), deadFxCmd)}
`,
hr,
`
`,
- ].join("");
+ ].join('');
sendChat(SCRIPT_NAME, `/w GM
${html}`);
}
/**
- * Renders the current settings panel publicly in game chat.
- * Used after setting-changing commands so players/DMs can see active config.
+ * Renders a read-only settings snapshot to public game chat (all players).
+ * Triggered by `!aura settings` on demand; not called automatically after changes.
*/
function showSettingsInGameChat() {
const s = state.HealthColors;
const hr = `
`;
const wrapStyle = [
- "border-radius:8px",
- "padding:5px",
- "font-size:9pt",
- "text-shadow:-1px -1px #222,1px -1px #222,-1px 1px #222,1px 1px #222,2px 2px #222",
- "box-shadow:3px 3px 1px #707070",
- "background-image:-webkit-linear-gradient(left,#76ADD6 0%,#a7c7dc 100%)",
- "color:#FFF",
- "border:2px solid black",
- "text-align:right",
- "vertical-align:middle",
- ].join(";");
+ 'border-radius:8px',
+ 'padding:5px',
+ 'font-size:9pt',
+ 'text-shadow:-1px -1px #222,1px -1px #222,-1px 1px #222,1px 1px #222,2px 2px #222',
+ 'box-shadow:3px 3px 1px #707070',
+ 'background-image:-webkit-linear-gradient(left,#76ADD6 0%,#a7c7dc 100%)',
+ 'color:#FFF',
+ 'border:2px solid black',
+ 'text-align:right',
+ 'vertical-align:middle',
+ ].join(';');
const percLabel = `${s.auraPercPC}/${s.auraPerc}`;
- const noStyle = "background-color:#A84D4D";
- const offStyle = "background-color:#D6D6D6";
- const pickNameStyle = (value) => {
- if (value === "No") return noStyle;
- if (value === "Off") return offStyle;
- return "";
- };
- const healStyle = `background-color:#${s.HealFX}`;
- const hurtStyle = `background-color:#${s.HurtFX}`;
const aura1Style = `background-color:#${s.Aura1Color}`;
const aura2Style = `background-color:#${s.Aura2Color}`;
+ const healStyle = `background-color:#${s.HealFX}`;
+ const hurtStyle = `background-color:#${s.HurtFX}`;
const html = [
``,
`HealthColors Settings: ${VERSION}
`,
hr,
- `Is On: ${makePill(s.auraColorOn ? "Yes" : "No", s.auraColorOn ? "" : "background-color:#A84D4D")}
`,
+ `Is On: ${boolPill(s.auraColorOn)}
`,
`Bar: ${makePill(s.auraBar)}
`,
- `Use Tint: ${makePill(s.auraTint ? "Yes" : "No", s.auraTint ? "" : "background-color:#A84D4D")}
`,
+ `Use Tint: ${boolPill(s.auraTint)}
`,
`Palette: ${makePill(s.colorPalette)}
`,
`Percentage(PC/NPC): ${makePill(percLabel)}
`,
hr,
- `Show PC Health: ${makePill(s.PCAura ? "Yes" : "No", s.PCAura ? "" : "background-color:#A84D4D")}
`,
- `Show NPC Health: ${makePill(s.NPCAura ? "Yes" : "No", s.NPCAura ? "" : "background-color:#A84D4D")}
`,
- `Show Dead PC: ${makePill(s.auraDeadPC ? "Yes" : "No", s.auraDeadPC ? "" : "background-color:#A84D4D")}
`,
- `Show Dead NPC: ${makePill(s.auraDead ? "Yes" : "No", s.auraDead ? "" : "background-color:#A84D4D")}
`,
+ `Show PC Health: ${boolPill(s.PCAura)}
`,
+ `Show NPC Health: ${boolPill(s.NPCAura)}
`,
+ `Show Dead PC: ${boolPill(s.auraDeadPC)}
`,
+ `Show Dead NPC: ${boolPill(s.auraDead)}
`,
hr,
- `GM Sees all PC Names: ${makePill(s.GM_PCNames, pickNameStyle(s.GM_PCNames))}
`,
- `GM Sees all NPC Names: ${makePill(s.GM_NPCNames, pickNameStyle(s.GM_NPCNames))}
`,
+ `GM Sees all PC Names: ${namePill(s.GM_PCNames)}
`,
+ `GM Sees all NPC Names: ${namePill(s.GM_NPCNames)}
`,
hr,
- `PC Sees all PC Names: ${makePill(s.PCNames, pickNameStyle(s.PCNames))}
`,
- `PC Sees all NPC Names: ${makePill(s.NPCNames, pickNameStyle(s.NPCNames))}
`,
+ `PC Sees all PC Names: ${namePill(s.PCNames)}
`,
+ `PC Sees all NPC Names: ${namePill(s.NPCNames)}
`,
hr,
`Aura 1 Radius: ${makePill(String(s.AuraSize))}
`,
`Aura 1 Shape: ${makePill(s.Aura1Shape)}
`,
@@ -1276,14 +1329,14 @@
`Aura 2 Radius: ${makePill(String(s.Aura2Size))}
`,
`Aura 2 Shape: ${makePill(s.Aura2Shape)}
`,
`Aura 2 Color: ${makePill(s.Aura2Color, aura2Style)}
`,
- `One Offs: ${makePill(s.OneOff ? "Yes" : "No", s.OneOff ? "" : "background-color:#A84D4D")}
`,
- `FX: ${makePill(s.FX ? "Yes" : "No", s.FX ? "" : "background-color:#A84D4D")}
`,
+ `One Offs: ${boolPill(s.OneOff)}
`,
+ `FX: ${boolPill(s.FX)}
`,
`HealFX Color: ${makePill(s.HealFX, healStyle)}
`,
`HurtFX Color: ${makePill(s.HurtFX, hurtStyle)}
`,
`DeathSFX: ${makePill(s.auraDeadFX)}
`,
hr,
`
`,
- ].join("");
+ ].join('');
sendChat(SCRIPT_NAME, `
${html}`);
}
@@ -1293,17 +1346,18 @@
* Processes incoming Roll20 chat messages to handle !aura commands.
* GM-only: non-GMs receive an access-denied whisper.
* Routes each subcommand (ON/OFF, BAR, TINT, PERC, PC, NPC, etc.) to the
- * appropriate state mutation then refreshes the menu. BAR validates 1/2/3,
- * whispers confirmation, and triggers immediate full sync. PALETTE also
- * triggers immediate full sync so existing tokens update right away.
- * When a setting changes, also posts a read-only settings snapshot to game chat.
- * Use `!aura settings` to output the current settings snapshot on demand.
+ * appropriate state mutation then refreshes the menu. BAR validates 1/2/3,
+ * whispers confirmation, and triggers immediate full sync. PALETTE also
+ * triggers immediate full sync so existing tokens update right away.
+ * When a setting changes, re-whispers the interactive menu to the GM.
+ * Use `!aura settings` to post a read-only settings snapshot to public game chat.
+ *
* @param {object} msg - Roll20 chat message object.
*/
function handleInput(msg) {
const parts = msg.content.split(/\s+/);
const command = parts[0].toUpperCase();
- if (msg.type !== "api" || !command.includes("!AURA")) return;
+ if (msg.type !== 'api' || !command.includes('!AURA')) return;
if (!playerIsGM(msg.playerid)) {
sendChat(
@@ -1313,203 +1367,129 @@
return;
}
- const option = (parts[1] || "MENU").toUpperCase();
- let changedSetting = false;
- if (option !== "MENU") gmWhisper("UPDATING TOKENS...");
-
- switch (option) {
- case "MENU":
- break;
- case "SETTINGS":
- showSettingsInGameChat();
- return;
- case "ON":
- state.HealthColors.auraColorOn = true;
- changedSetting = true;
- break;
- case "OFF":
- state.HealthColors.auraColorOn = false;
- changedSetting = true;
- break;
- case "BAR":
- if (/^[123]$/.test(parts[2] || "")) {
- state.HealthColors.auraBar = `bar${parts[2]}`;
- changedSetting = true;
- gmWhisper(
- `Health bar set to ${state.HealthColors.auraBar}. Forcing sync...`,
- );
- menuForceUpdate();
- } else {
- gmWhisper(
- `Invalid bar "${parts[2] || ""}". Use !aura bar 1, !aura bar 2, or !aura bar 3.`,
- );
- }
- break;
- case "TINT":
- state.HealthColors.auraTint = !state.HealthColors.auraTint;
- changedSetting = true;
- break;
- case "PERC":
- state.HealthColors.auraPercPC = Number.parseInt(parts[2], 10);
- state.HealthColors.auraPerc = Number.parseInt(parts[3], 10);
- changedSetting = true;
- break;
- case "PC":
- state.HealthColors.PCAura = !state.HealthColors.PCAura;
- changedSetting = true;
- break;
- case "NPC":
- state.HealthColors.NPCAura = !state.HealthColors.NPCAura;
- changedSetting = true;
- break;
- case "GMNPC":
- state.HealthColors.GM_NPCNames = parts[2];
- changedSetting = true;
- break;
- case "GMPC":
- state.HealthColors.GM_PCNames = parts[2];
- changedSetting = true;
- break;
- case "PCNPC":
- state.HealthColors.NPCNames = parts[2];
- changedSetting = true;
- break;
- case "PCPC":
- state.HealthColors.PCNames = parts[2];
- changedSetting = true;
- break;
- case "DEAD":
- state.HealthColors.auraDead = !state.HealthColors.auraDead;
- changedSetting = true;
- break;
- case "DEADPC":
- state.HealthColors.auraDeadPC = !state.HealthColors.auraDeadPC;
- changedSetting = true;
- break;
- case "DEADFX":
- state.HealthColors.auraDeadFX = parts[2];
- changedSetting = true;
- break;
- case "SIZE":
- state.HealthColors.AuraSize = Number.parseFloat(parts[2]);
- changedSetting = true;
- break;
- case "A1SHAPE":
- state.HealthColors.Aura1Shape = normalizeShape(
- parts[2],
- state.HealthColors.Aura1Shape,
- );
- changedSetting = true;
- break;
- case "A1TINT":
- state.HealthColors.Aura1Color = normalizeHex6(
- parts[2],
- state.HealthColors.Aura1Color,
- );
- changedSetting = true;
- break;
- case "A2SIZE":
- state.HealthColors.Aura2Size = Number.parseFloat(parts[2]);
- changedSetting = true;
- break;
- case "A2SHAPE":
- state.HealthColors.Aura2Shape = normalizeShape(
- parts[2],
- state.HealthColors.Aura2Shape,
- );
- changedSetting = true;
- break;
- case "A2TINT":
- state.HealthColors.Aura2Color = normalizeHex6(
- parts[2],
- state.HealthColors.Aura2Color,
- );
- changedSetting = true;
- break;
- case "PALETTE":
- state.HealthColors.colorPalette = normalizePalette(
- parts[2],
- state.HealthColors.colorPalette,
- );
- menuForceUpdate();
- changedSetting = true;
- break;
- case "ONEOFF":
- state.HealthColors.OneOff = !state.HealthColors.OneOff;
- changedSetting = true;
- break;
- case "FX":
- state.HealthColors.FX = !state.HealthColors.FX;
- changedSetting = true;
- break;
- case "HEAL":
- state.HealthColors.HealFX = parts[2].toUpperCase();
- syncDefaultFxObjects();
- changedSetting = true;
- break;
- case "HURT":
- state.HealthColors.HurtFX = parts[2].toUpperCase();
- syncDefaultFxObjects();
- changedSetting = true;
- break;
- case "RESET":
- delete state.HealthColors;
- gmWhisper("STATE RESET");
- checkInstall();
- changedSetting = true;
- break;
- case "RESET-FX":
- resetDefaultFxObjects();
- break;
- case "RESET-ALL":
- runResetAllFlow();
- changedSetting = true;
- break;
- case "FORCEALL":
- menuForceUpdate();
- return;
- case "UPDATE":
- manUpdate(msg);
- return;
- }
+ const option = (parts[1] || 'MENU').toUpperCase();
+ if (option !== 'MENU') gmWhisper('UPDATING TOKENS...');
- if (changedSetting) {
- showSettingsInGameChat();
+ const s = state.HealthColors;
+
+ // Dispatch tables for structurally identical cases
+ const TOGGLES = {
+ TINT: 'auraTint', PC: 'PCAura', NPC: 'NPCAura',
+ DEAD: 'auraDead', DEADPC: 'auraDeadPC', ONEOFF: 'OneOff', FX: 'FX',
+ };
+ const STRINGS = {
+ GMNPC: 'GM_NPCNames', GMPC: 'GM_PCNames',
+ PCNPC: 'NPCNames', PCPC: 'PCNames', DEADFX: 'auraDeadFX',
+ };
+ const FLOATS = { SIZE: 'AuraSize', A2SIZE: 'Aura2Size' };
+ const SHAPES = { A1SHAPE: 'Aura1Shape', A2SHAPE: 'Aura2Shape' };
+ const HEXES = { A1TINT: 'Aura1Color', A2TINT: 'Aura2Color' };
+
+ if (TOGGLES[option]) {
+ s[TOGGLES[option]] = !s[TOGGLES[option]];
+ } else if (STRINGS[option]) {
+ s[STRINGS[option]] = parts[2];
+ } else if (FLOATS[option]) {
+ s[FLOATS[option]] = Number.parseFloat(parts[2]);
+ } else if (SHAPES[option]) {
+ s[SHAPES[option]] = normalizeShape(parts[2], s[SHAPES[option]]);
+ } else if (HEXES[option]) {
+ s[HEXES[option]] = normalizeHex6(parts[2], s[HEXES[option]]);
} else {
- showMenu();
+ switch (option) {
+ case 'MENU':
+ break;
+ case 'SETTINGS':
+ showSettingsInGameChat();
+ return;
+ case 'ON':
+ s.auraColorOn = true;
+ break;
+ case 'OFF':
+ s.auraColorOn = false;
+ break;
+ case 'BAR':
+ if (/^[123]$/.test(parts[2] || '')) {
+ s.auraBar = `bar${parts[2]}`;
+ gmWhisper(`Health bar set to ${s.auraBar}. Forcing sync...`);
+ menuForceUpdate();
+ } else {
+ gmWhisper(
+ `Invalid bar "${parts[2] || ''}". Use !aura bar 1, !aura bar 2, or !aura bar 3.`,
+ );
+ }
+ break;
+ case 'PERC':
+ s.auraPercPC = Number.parseInt(parts[2], 10);
+ s.auraPerc = Number.parseInt(parts[3], 10);
+ menuForceUpdate();
+ break;
+ case 'PALETTE':
+ s.colorPalette = normalizePalette(parts[2], s.colorPalette);
+ menuForceUpdate();
+ break;
+ case 'HEAL':
+ s.HealFX = parts[2].toUpperCase();
+ syncDefaultFxObjects();
+ break;
+ case 'HURT':
+ s.HurtFX = parts[2].toUpperCase();
+ syncDefaultFxObjects();
+ break;
+ case 'RESET':
+ delete state.HealthColors;
+ gmWhisper('STATE RESET');
+ checkInstall();
+ break;
+ case 'RESET-FX':
+ resetDefaultFxObjects();
+ break;
+ case 'RESET-ALL':
+ runResetAllFlow();
+ break;
+ case 'FORCEALL':
+ menuForceUpdate();
+ return;
+ case 'UPDATE':
+ manUpdate(msg);
+ return;
+ }
}
+
+ showMenu();
}
// ————— OUTSIDE API —————
/**
* Public entry point for external scripts to request a token color update.
* Validates that the object is a graphic before delegating to handleToken.
+ *
* @param {object} obj - Roll20 object to update.
* @param {object} prev - Previous attribute snapshot (passed through to handleToken).
*/
function updateToken(obj, prev) {
- if (obj.get("type") === "graphic") {
+ if (obj.get('type') === 'graphic') {
handleToken(obj, prev);
} else {
- gmWhisper("Script sent non-Token to be updated!");
+ gmWhisper('Script sent non-Token to be updated!');
}
}
// ————— EVENT HANDLERS —————
/**
* Registers all Roll20 event listeners for the script.
- * - chat:message → handleInput (command processing)
- * - change:token → handleToken (live HP changes)
- * - add:token → handleToken (with 400ms delay to allow token data to settle)
+ * - chat:message → handleInput (command processing)
+ * - change:graphic → handleToken (live HP changes and token resizes)
+ * - add:token → handleToken (with 400ms delay to allow token data to settle)
*/
function registerEventHandlers() {
- on("chat:message", handleInput);
- on("change:graphic", handleToken);
- on("add:token", (t) => {
+ on('chat:message', handleInput);
+ on('change:graphic', handleToken);
+ on('add:token', (t) => {
setTimeout(() => {
- const token = getObj("graphic", t.id);
- const prev = JSON.parse(JSON.stringify(token));
- handleToken(token, prev, "YES");
+ const token = getObj('graphic', t.id);
+ const prev = deepClone(token);
+ handleToken(token, prev, 'YES');
}, 400);
});
}
@@ -1522,7 +1502,7 @@
registerEventHandlers,
};
- on("ready", () => {
+ on('ready', () => {
gmWhisper(`MOD READY (v${VERSION})`);
checkInstall();
registerEventHandlers();
diff --git a/HealthColors/README.md b/HealthColors/README.md
index 1b723d47d..1aff98d70 100644
--- a/HealthColors/README.md
+++ b/HealthColors/README.md
@@ -32,36 +32,43 @@
### The Configuration Menu
Type `!aura` in the chat to open the interactive configuration menu. From here, you can toggle all features and adjust percentages.
-When a command changes a setting, HealthColors posts a single read-only settings snapshot to game chat (instead of duplicating menu output).
+When a command changes a setting, HealthColors re-whispers the interactive GM menu so you can continue adjusting without re-typing `!aura`. Use `!aura settings` to post a read-only public snapshot to game chat on demand.
### Command Reference
-| Command | Description |
-| :-------------------------- | :--------------------------------------------------------------------------------- |
-| `!aura` | Opens the main configuration menu. |
-| `!aura settings` | Outputs the current HealthColors settings snapshot to game chat. |
-| `!aura forceall` | Forces a visual sync for every token on the current map. |
-| `!aura on/off` | Enables or disables the script globally. |
-| `!aura tint` | Toggles between **Aura 1** mode and **Tint** mode. |
-| `!aura palette ` | Sets the health palette (`default` or `colorblind`) and auto-refreshes all tokens. |
-| `!aura size ` | Sets Aura 1 radius in feet from token edge (e.g., `!aura size 0.35`). |
-| `!aura a1shape ` | Sets Aura 1 display shape (`Circle`, `Square`). |
-| `!aura a1tint ` | Sets Aura 1 display tint color (e.g., `!aura a1tint 00FF00`). |
-| `!aura a2size ` | Sets Aura 2 display radius (e.g., `!aura a2size 5`). |
-| `!aura a2shape ` | Sets Aura 2 display shape (`Square`, `Circle`). |
-| `!aura a2tint ` | Sets Aura 2 display tint color (e.g., `!aura a2tint 806600`). |
-| `!aura bar <1/2/3>` | Sets which token bar represents health and immediately forces a full token sync. |
-| `!aura pc / !aura npc` | Toggles health tracking for PCs or NPCs. |
-| `!aura perc ` | Sets the health percentage at which the aura appears (e.g., `!aura perc 100 100`). |
-| `!aura dead / !aura deadPC` | Toggles the automatic "Dead" status marker. |
-| `!aura fx` | Toggles particle effects for damage and healing. |
-| `!aura heal ` | Sets the color of healing particle effects (e.g., `!aura heal FDDC5C`). |
-| `!aura hurt ` | Sets the color of damage particle effects (e.g., `!aura hurt FF0000`). |
-| `!aura reset` | Resets the script's state to factory defaults. |
-| `!aura reset-fx` | Rebuilds `-DefaultHeal` and `-DefaultHurt` custom FX objects. |
-| `!aura reset-all` | Restores all settings to `DEFAULTS`, rebuilds default FX, and force-syncs tokens. |
-
-### Health Palettes
+| Command | Description |
+| :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| `!aura` | Opens the main configuration menu. |
+| `!aura settings` | Outputs the current HealthColors settings snapshot to game chat. |
+| `!aura forceall` | Forces a visual sync for every token on the current map. |
+| `!aura on/off` | Enables or disables the script globally. |
+| `!aura tint` | Toggles between **Aura 1** mode and **Tint** mode. |
+| `!aura palette ` | Sets the health palette (`default` or `colorblind`) and auto-refreshes all tokens. |
+| `!aura size ` | Sets Aura 1 radius in feet from token edge (e.g., `!aura size 0.35`). |
+| `!aura a1shape ` | Sets Aura 1 display shape (`Circle`, `Square`). |
+| `!aura a1tint ` | Sets Aura 1 display tint color (e.g., `!aura a1tint 00FF00`). |
+| `!aura a2size ` | Sets Aura 2 display radius (e.g., `!aura a2size 5`). |
+| `!aura a2shape ` | Sets Aura 2 display shape (`Square`, `Circle`). |
+| `!aura a2tint ` | Sets Aura 2 display tint color (e.g., `!aura a2tint 806600`). |
+| `!aura bar <1/2/3>` | Sets which token bar represents health and immediately forces a full token sync. |
+| `!aura pc / !aura npc` | Toggles health tracking for PCs or NPCs. |
+| `!aura perc ` | Sets aura/tint visibility threshold: `0` disables, `1-99` shows at or below that HP%, `100` always visible for living tokens. |
+| `!aura dead / !aura deadPC` | Toggles the automatic "Dead" status marker. |
+| `!aura gmpc ` | Sets GM visibility of PC token nameplates (`Yes` = always show, `No` = always hide, `Off` = use Roll20 default). |
+| `!aura gmnpc ` | Sets GM visibility of NPC token nameplates (`Yes` = always show, `No` = always hide, `Off` = use Roll20 default). |
+| `!aura pcpc ` | Sets player visibility of PC token nameplates (`Yes` = always show, `No` = always hide, `Off` = use Roll20 default). |
+| `!aura pcnpc ` | Sets player visibility of NPC token nameplates (`Yes` = always show, `No` = always hide, `Off` = use Roll20 default). |
+| `!aura oneoff` | Toggles health tracking for tokens that are not linked to a character sheet. |
+| `!aura update` | Forces a health-color update on all currently selected tokens. |
+| `!aura fx` | Toggles particle effects for damage and healing. |
+| `!aura heal ` | Sets the color of healing particle effects (e.g., `!aura heal FDDC5C`). |
+| `!aura hurt ` | Sets the color of damage particle effects (e.g., `!aura hurt FF0000`). |
+| `!aura deadfx ` | Sets a jukebox track to play when a token dies (e.g., `!aura deadfx Funeral`), or `None` to disable. |
+| `!aura reset` | Resets the script's state to factory defaults. |
+| `!aura reset-fx` | Rebuilds `-DefaultHeal` and `-DefaultHurt` custom FX objects. |
+| `!aura reset-all` | Restores all settings to `DEFAULTS`, rebuilds default FX, and force-syncs tokens. |
+
+### HealthColor Palettes
- `default`: High = Green, Mid = Yellow, Low = Red, Dead = Black.
- `colorblind`: High = Cyan, Mid = Orange, Low = Magenta, Dead = Black.
@@ -84,4 +91,5 @@ When a command changes a setting, HealthColors posts a single read-only settings
## Credits
Original Author: DXWarlock
+Version 1.7.1 edit by Surok
Refactored and Modernized for v2 by MidNiteShadow7
diff --git a/HealthColors/script.json b/HealthColors/script.json
index 9921aeb41..a5157cfcf 100644
--- a/HealthColors/script.json
+++ b/HealthColors/script.json
@@ -1,10 +1,12 @@
{
"name": "Aura/Tint HealthColors",
"script": "HealthColors.js",
- "version": "2.1.0",
+ "version": "2.1.1",
"previousversions": [
+ "2.1.0",
"2.0.1",
"2.0.0",
+ "1.7.1",
"1.6.1",
"1.6.0",
"1.5.1",
@@ -15,8 +17,8 @@
"1.2.0"
],
"description": "Script to set health indicator on tokens via aura or tint, based on a bars value. Be sure to set the bar your health is tied to in the menu: API menu command !AURA",
- "authors": "DXWarlock, MidNiteShadow7",
- "roll20userid": "262130, 16506286",
+ "authors": "DXWarlock, Surok, MidNiteShadow7",
+ "roll20userid": "262130, 335573, 16506286",
"dependencies": [],
"modifies": {
"custfx": "read,write",