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

Help with Aura/Tint HealthColors API

I was hoping someone could help me. In advance: I'm not a scripter. I just try to figure things out by reverse-engeering and trial&error.^^" Yesterday I switched to Jumpgate and since then the DefaultHurt and DefauftHeal FX have been pure white whenever triggered by changing a tokens HP values. I tried pretty much anything I could think of.. Changing the FX colour settings, fiddling with !aura menu... Checking the character settings... Even using a modded version from Aaron that frees up bar2... Nothing works. In the legacy instance of my campaign the API functions normally. So it's definitely Jumpgate that made the API's behavior change. Could someone help me out with this problem? Or is so that I need to wait for a Roll20/Jumpgate update to fix this? Kind regards
1736918975
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Hi Crossfade! You might try switching to "Experimental" on the mods page. What sheet is your game using?
Switching to experimental did not change anything. And I'm using the 2014 DnD 5e character sheets.
No other thoughts/ideas? :( Couldn't someone double check if it's the same in their game and what happens to the Script itself on Jumpgate?
I use this in all of my games. The health ring is still working, but I too get the white 'blood' spurts when damage happens. I would wager a guess that the API points at a specific effect or color built in, and the Jumpgate version has mixed around the reference points. I'm not a coder either. I think that all we can do is wait for the creator to update the API. I'm willing to bet that will be the case with a lot of them. Have you reached out to the creator yet?
1737151248

Edited 1737151279
IMMERSION said: Have you reached out to the creator yet? The script author for Aura/Tint Healthcolors (DXWarlock) hasn't been active on the forums for over 4 years, and originally wrote the script 9 years ago. :(
Hi!! I just want to echo that I am also having the same issue!! The tint/aura works fine, but the special effects are splattering white in my new Jumpgate game instead of the holy yellow for heal and the blood red for hurt. Fortunately not game breaking, but I would love to know how to fix this if at all possible.
1738923738

Edited 1738923772
IMMERSION said: I use this in all of my games. The health ring is still working, but I too get the white 'blood' spurts when damage happens. I would wager a guess that the API points at a specific effect or color built in, and the Jumpgate version has mixed around the reference points. I'm not a coder either. I think that all we can do is wait for the creator to update the API. I'm willing to bet that will be the case with a lot of them. Have you reached out to the creator yet? I have not. As Jarren pointed out the creator has not been active in a long time.. I did however reached out to TheAaron and he said he will take a look when he has the time to do so.
1738941363
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
If he does, it is my fervent hope that he would strip out the code controlling name visibility and some other superfluous functions. That script causes a lot of false bug reports.
I've been using Deathtracker instead. It's has functional fx and handles hp auras like Aura/Tint HealthColors.
I updated the AURA code to use the FX deathtracker uses. This version doesn't use aura 2 (I had removed aura 2 because I needed it for other auras for spells etc). Try it and let me know how it goes. /* global createObj TokenMod spawnFxWithDefinition getObj state playerIsGM sendChat _ findObjs log on*/ /* My Profile link: <a href="https://app.roll20.net/users/262130/dxwarlock" rel="nofollow">https://app.roll20.net/users/262130/dxwarlock</a> GIT link: <a href="https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors" rel="nofollow">https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors</a> Roll20Link: <a href="https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor" rel="nofollow">https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor</a> Version 1.6.1 - Feb 16 2025 - Updated FX effects: The damage/healing FX now use the simpler DeathTracker approach. (Damage FX uses the HurtFX type (default "splatter-blood") and Healing FX uses the HealFX type (default "glow-holy").) */ /*jshint bitwise: false*/ var HealthColors = HealthColors || (function () { 'use strict'; var version = '1.6.1', ScriptName = "HealthColors", schemaVersion = '1.0.3', Updated = "Feb 16 2025", /*------------------------ ON TOKEN CHANGE/CREATE ------------------------*/ handleToken = function (obj, prev, update) { //CHECK IF TRIGGERED------------ if(state.HealthColors.auraColorOn !== true || obj.get("layer") !== "objects") return; if(obj.get("represents") !== "" || (obj.get("represents") === "" &amp;&amp; state.HealthColors.OneOff === true)) { //**CHECK BARS------------// var barUsed = state.HealthColors.auraBar; var maxValue, curValue, prevValue; if(obj.get(barUsed + "_max") !== "" || obj.get(barUsed + "_value") !== "") { maxValue = parseInt(obj.get(barUsed + "_max"), 10); curValue = parseInt(obj.get(barUsed + "_value"), 10); prevValue = prev[barUsed + "_value"]; } if(isNaN(maxValue) || isNaN(curValue) || isNaN(prevValue)) return; //CALC PERCENTAGE------------ var percReal = Math.round((curValue / maxValue) * 100); var markerColor = PercentToHEX(percReal); //DEFINE VARIABLES--- var pColor = '#ffffff'; var GM = '', PC = ''; var IsTypeOn, PercentOn, ShowDead, UseAura; //**CHECK MONSTER OR PLAYER------------// var oCharacter = getObj('character', obj.get("_represents")); var type = (oCharacter === undefined || oCharacter.get("controlledby") === "") ? 'Monster' : 'Player'; var colortype = (state.HealthColors.auraTint) ? 'tint' : 'aura1'; //IF PLAYER------------ if(type == 'Player') { GM = state.HealthColors.GM_PCNames; PC = state.HealthColors.PCNames; IsTypeOn = state.HealthColors.PCAura; PercentOn = state.HealthColors.auraPercPC; ShowDead = state.HealthColors.auraDeadPC; var cBy = oCharacter.get('controlledby'); var player = getObj('player', cBy); pColor = '#000000'; if(player !== undefined) pColor = player.get('color'); } //IF MONSTER------------ else if(type == 'Monster') { GM = state.HealthColors.GM_NPCNames; PC = state.HealthColors.NPCNames; IsTypeOn = state.HealthColors.NPCAura; PercentOn = state.HealthColors.auraPerc; ShowDead = state.HealthColors.auraDead; } else return; //CHECK DISABLED AURA/TINT ATTRIB------------ if(oCharacter !== undefined) { UseAura = lookupUseColor(oCharacter); } //SET HEALTH COLOR---------- if(IsTypeOn &amp;&amp; UseAura !== "NO") { percReal = Math.min(percReal, 100); if(percReal &gt; PercentOn || curValue === 0) SetAuraNone(obj); else TokenSet(obj, state.HealthColors.AuraSize, markerColor, pColor, update); //SHOW DEAD---------- if(ShowDead === true) { if(curValue &gt; 0) obj.set("status_dead", false); else if(curValue &lt; 1) { var DeadSounds = state.HealthColors.auraDeadFX; if(DeadSounds !== "None" &amp;&amp; curValue != prevValue) PlayDeath(DeadSounds); obj.set("status_dead", true); SetAuraNone(obj); } } } else if((!IsTypeOn || UseAura === "NO") &amp;&amp; obj.get(colortype + '_color') === markerColor) SetAuraNone(obj); //SET SHOW NAMES------------ SetShowNames(GM, PC, obj); //**FX for Damage/Healing using DeathTracker's approach------------ if(curValue != prevValue &amp;&amp; prevValue != "" &amp;&amp; update !== "YES") { let left = parseInt(obj.get('left')), top = parseInt(obj.get('top')); if(state.HealthColors.FX === true) { if(curValue &lt; prevValue) { // Damage FX – using HurtFX type (default: splatter-blood) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HurtFX, obj.get('_pageid')); } else if(curValue &gt; prevValue) { // Healing FX – using HealFX type (default: glow-holy) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HealFX, obj.get('_pageid')); } } } } }, /*------------------------ CHAT MESSAGES ------------------------*/ handleInput = function (msg) { var msgFormula = msg.content.split(/\s+/); var command = msgFormula[0].toUpperCase(), UPPER =""; if(msg.type == "api" &amp;&amp; command.indexOf("!AURA") !== -1) { var OPTION = msgFormula[1] || "MENU"; if(!playerIsGM(msg.playerid)) { sendChat('HealthColors', "/w " + msg.who + " you must be a GM to use this command!"); return; } else { if(OPTION !== "MENU") GMW("UPDATING TOKENS..."); switch(OPTION.toUpperCase()) { case "MENU": break; case "ON": state.HealthColors.auraColorOn = !state.HealthColors.auraColorOn; break; case "BAR": state.HealthColors.auraBar = "bar" + msgFormula[2]; break; case "TINT": state.HealthColors.auraTint = !state.HealthColors.auraTint; break; case "PERC": state.HealthColors.auraPercPC = parseInt(msgFormula[2], 10); state.HealthColors.auraPerc = parseInt(msgFormula[3], 10); break; case "PC": state.HealthColors.PCAura = !state.HealthColors.PCAura; break; case "NPC": state.HealthColors.NPCAura = !state.HealthColors.NPCAura; break; case "GMNPC": state.HealthColors.GM_NPCNames = msgFormula[2]; break; case "GMPC": state.HealthColors.GM_PCNames = msgFormula[2]; break; case "PCNPC": state.HealthColors.NPCNames = msgFormula[2]; break; case "PCPC": state.HealthColors.PCNames = msgFormula[2]; break; case "DEAD": state.HealthColors.auraDead = !state.HealthColors.auraDead; break; case "DEADPC": state.HealthColors.auraDeadPC = !state.HealthColors.auraDeadPC; break; case "DEADFX": state.HealthColors.auraDeadFX = msgFormula[2]; break; case "SIZE": state.HealthColors.AuraSize = parseFloat(msgFormula[2]); break; case "ONEOFF": state.HealthColors.OneOff = !state.HealthColors.OneOff; break; case "FX": state.HealthColors.FX = !state.HealthColors.FX; break; case "HEAL": UPPER = msgFormula[2]; UPPER = UPPER.toLowerCase(); state.HealthColors.HealFX = UPPER; break; case "HURT": UPPER = msgFormula[2]; UPPER = UPPER.toLowerCase(); state.HealthColors.HurtFX = UPPER; break; case "RESET": delete state.HealthColors; GMW("STATE RESET"); checkInstall(); break; case "UPDATE": manUpdate(msg); return; } aurahelp(OPTION); } } }, /*------------------------ "FUNCTIONS" ------------------------*/ //SET TOKEN COLORS------------ TokenSet = function (obj, sizeSet, markerColor, pColor, update) { var Pageon = getObj("page", obj.get("_pageid")); var scale = Pageon.get("scale_number") / 10; if(state.HealthColors.auraTint === true) { if(obj.get('aura1_color') == markerColor &amp;&amp; update === "YES") { obj.set({'aura1_color': "transparent",}); } obj.set({'tint_color': markerColor,}); } else { if(obj.get('tint_color') == markerColor &amp;&amp; update === "YES") { obj.set({'tint_color': "transparent",}); } obj.set({ 'aura1_radius': sizeSet * scale * 1.8, 'aura1_color': markerColor, 'showplayers_aura1': true, }); } }, //REMOVE ALL------------ SetAuraNone = function (obj) { if(state.HealthColors.auraTint === true) obj.set({'tint_color': "transparent",}); else obj.set({'aura1_color': "transparent"}); }, //FORCE ALL TOKEN UPDATE------------ MenuForceUpdate = function(){ let i = 0; const start = new Date().getTime(); const barUsed = state.HealthColors.auraBar; const workQueue = findObjs({type: 'graphic',subtype: 'token',layer: 'objects'}) .filter((o)=&gt;o.get(barUsed + "_max") !== "" &amp;&amp; o.get(barUsed + "_value") !== ""); const drainQueue = ()=&gt;{ let t = workQueue.shift(); if(t){ const prev = JSON.parse(JSON.stringify(t)); handleToken( t, prev, 'YES'); setTimeout(drainQueue,0); } else { sendChat('Fixing Tokens',`/w gm Finished Fixing Tokens`); } }; sendChat('Fixing Tokens',`/w gm Fixing ${workQueue.length} Tokens`); drainQueue(); var end = new Date().getTime(); return "Tokens Processed: " + workQueue.length + "&lt;br&gt;Run time in ms: " + (end - start); }, SetShowNames = function(GM, PC, obj) { if(GM != 'Off' &amp;&amp; GM != '') { GM = (GM == "Yes") ? true : false; obj.set({'showname': GM}); } if(PC != 'Off' &amp;&amp; PC != '') { PC = (PC == "Yes") ? true : false; obj.set({'showplayers_name': PC}); } }, //MANUAL UPDATE------------ manUpdate = function(msg){ var selected = msg.selected; var allNames = ''; _.each(selected, function(obj) { var token = getObj('graphic', obj._id); var tName = token.get("name"); allNames = allNames.concat(tName+'&lt;br&gt;'); var prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }); GMW(allNames); }, //ATTRIBUTE CACHE------------ makeSmartAttrCache = function (attribute, options) { let cache = {}, defaultValue = options.default || 'YES', validator = options.validation || _.constant(true); on('change:attribute', function (attr) { if(attr.get('name') === attribute) { if(!validator(attr.get('current'))) { attr.set('current', defaultValue); } cache[attr.get('characterid')] = attr.get('current'); var tokens = findObjs({type: 'graphic'}).filter((o) =&gt; o.get('represents') === attr.get("characterid")); _.each(tokens, function (obj) { var prev = JSON.parse(JSON.stringify(obj)); handleToken(obj, prev, "YES"); }); } }); on('destroy:attribute', function (attr) { if(attr.get('name') === attribute) { delete cache[attr.get('characterid')]; } }); return function(character){ let attr = findObjs({type: 'attribute',name: attribute,characterid: character.id},{caseInsensitive:true})[0] || 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'); } return cache[character.id]; }; }, lookupUseBlood = makeSmartAttrCache('USEBLOOD',{ default: 'DEFAULT' }), lookupUseColor = makeSmartAttrCache('USECOLOR',{ default: 'YES', validation: (o)=&gt;o.match(/YES|NO/) }), //DEATH SOUND------------ PlayDeath = function (trackname) { var RandTrackName; if(trackname.indexOf(",") &gt; 0) { var tracklist = trackname.split(","); RandTrackName = tracklist[Math.floor(Math.random() * tracklist.length)]; } else RandTrackName = trackname; var track = findObjs({type: 'jukeboxtrack',title: RandTrackName})[0]; if(track) { track.set('playing', false); track.set('softstop', false); track.set('volume', 50); track.set('playing', true); } else { log(ScriptName + ": No track found named " + RandTrackName); } }, //PERC TO RGB------------ PercentToHEX = function (percent) { var HEX; if(percent &gt; 100) HEX = "#0000FF"; else { if(percent === 100) percent = 99; var r, g, b = 0; if(percent &lt; 50) { g = Math.floor(255 * (percent / 50)); r = 255; } else { g = 255; r = Math.floor(255 * ((50 - percent % 50) / 50)); } HEX = "#" + ((1 &lt;&lt; 24) + (r &lt;&lt; 16) + (g &lt;&lt; 8) + b).toString(16).slice(1); } return HEX; }, //HEX TO RGB------------ HEXtoRGB = function (hex) { let parts = (hex || '').match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); if(parts) { let rgb = _.chain(parts).rest().map((d) =&gt; parseInt(d, 16)).value(); rgb.push(1.0); return rgb; } return [0, 0, 0, 0.0]; }, //SPAWN FX------------ SpawnFX = function (Scale,HitSize,left,top,FX,pageid) { _.defaults(FX, { "maxParticles": 100, "duration": 100, "size": 100, "sizeRandom": 100, "lifeSpan": 100, "lifeSpanRandom": 100, "speed": 0, "speedRandom": 0, "angle": 0, "angleRandom": 0, "emissionRate": 100, "startColour": [255,255,255,1], "endColour": [0,0,0,1], "gravity": {"x": 0,"y": 0.0}, }); var newFX = { "maxParticles": FX.maxParticles * HitSize, "duration": FX.duration * HitSize, "size": FX.size * Scale / 2, "sizeRandom": FX.sizeRandom * Scale / 2, "lifeSpan": FX.lifeSpan, "lifeSpanRandom": FX.lifeSpanRandom, "speed": FX.speed * Scale, "speedRandom": FX.speedRandom * Scale, "angle": FX.angle, "angleRandom": FX.angleRandom, "emissionRate": FX.emissionRate * HitSize * 2, "startColour": FX.startColour, "endColour": FX.endColour, "gravity": {"x": FX.gravity.x * Scale,"y": FX.gravity.y * Scale}, }; spawnFxWithDefinition(left,top,newFX,pageid); }, //HELP MENU------------ aurahelp = function (OPTION) { var Update = ''; if(OPTION !== "MENU") Update = MenuForceUpdate(); var img = "background-image: -webkit-linear-gradient(left, #76ADD6 0%, #a7c7dc 100%);"; var tshadow = "-1px -1px #222, 1px -1px #222, -1px 1px #222, 1px 1px #222 , 2px 2px #222;"; // Original style for most buttons: var style = 'style="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;"'; // New style for FX Type buttons with a wider width: var fxStyle = 'style="padding-top: 1px; text-align:center; font-size: 9pt; width: 80px; height: 14px; border: 1px solid black; margin: 1px; background-color: #6FAEC7;border-radius: 4px; box-shadow: 1px 1px 1px #707070;"'; var off = "#A84D4D"; var disable = "#D6D6D6"; var HR = "&lt;hr style='background-color: #000000; margin: 5px; border-width:0;color: #000000;height: 1px;'/&gt;"; var FX = state.HealthColors.auraDeadFX.substring(0, 4); sendChat('HealthColors', "/w GM &lt;b&gt;&lt;br&gt;" + '&lt;div style="border-radius: 8px 8px 8px 8px; padding: 5px; font-size: 9pt; text-shadow: ' + tshadow + '; box-shadow: 3px 3px 1px #707070; ' + img + ' color:#FFF; border:2px solid black; text-align:right; vertical-align:middle;"&gt;' + '&lt;u&gt;&lt;big&gt;HealthColors Version: ' + version + '&lt;/u&gt;&lt;/big&gt;&lt;br&gt;' + HR + 'Is On: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraColorOn !== true ? off : "") + ';" href="!aura on"&gt;' + (state.HealthColors.auraColorOn !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Bar: &lt;a ' + style + '" href="!aura bar ?{Bar|1|2|3}"&gt;' + state.HealthColors.auraBar + '&lt;/a&gt;&lt;br&gt;' + 'Use Tint: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraTint !== true ? off : "") + ';" href="!aura tint"&gt;' + (state.HealthColors.auraTint !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Percentage(PC/NPC): &lt;a ' + style + '" href="!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}"&gt;' + state.HealthColors.auraPercPC + '/'+ state.HealthColors.auraPerc +'&lt;/a&gt;&lt;br&gt;' + HR + 'Show PC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.PCAura !== true ? off : "") + ';" href="!aura pc"&gt;' + (state.HealthColors.PCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show NPC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.NPCAura !== true ? off : "") + ';" href="!aura npc"&gt;' + (state.HealthColors.NPCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead PC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDeadPC !== true ? off : "") + ';" href="!aura deadPC"&gt;' + (state.HealthColors.auraDeadPC !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead NPC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDead !== true ? off : "") + ';" href="!aura dead"&gt;' + (state.HealthColors.auraDead !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + HR + 'GM Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_PCNames, off, disable) + ';" href="!aura gmpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_PCNames + '&lt;/a&gt;&lt;br&gt;' + 'GM Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_NPCNames, off, disable) + ';" href="!aura gmnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'PC Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.PCNames, off, disable) + ';" href="!aura pcpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.PCNames + '&lt;/a&gt;&lt;br&gt;' + 'PC Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.NPCNames, off, disable) + ';" href="!aura pcnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'Aura Size: &lt;a ' + style + '" href="!aura size ?{Size?|0.7}"&gt;' + state.HealthColors.AuraSize + '&lt;/a&gt;&lt;br&gt;' + 'One Offs: &lt;a ' + style + 'background-color:' + (state.HealthColors.OneOff !== true ? off : "") + ';" href="!aura ONEOFF"&gt;' + (state.HealthColors.OneOff !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'FX: &lt;a ' + style + 'background-color:' + (state.HealthColors.FX !== true ? off : "") + ';" href="!aura FX"&gt;' + (state.HealthColors.FX !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'HealFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HealFX + ';" href="!aura HEAL ?{FX Type?|glow-holy}"&gt;' + state.HealthColors.HealFX + '&lt;/a&gt;&lt;br&gt;' + 'HurtFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HurtFX + ';" href="!aura HURT ?{FX Type?|splatter-blood}"&gt;' + state.HealthColors.HurtFX + '&lt;/a&gt;&lt;br&gt;' + 'DeathSFX: &lt;a ' + style + '" href="!aura deadfx ?{Sound Name?|' + state.HealthColors.auraDeadFX + '}"&gt;' + FX + '&lt;/a&gt;&lt;br&gt;' + HR + Update + '&lt;/div&gt;'); }, //OFF BUTTON COLORS------------ ButtonColor = function (state, off, disable) { var color; if(state == "No") color = off; if(state == "Off") color = disable; return color; }, //CHECK INSTALL &amp; SET STATE------------ checkInstall = function () { log('-=&gt;' + ScriptName + ' v' + version + ' [Updated: ' + Updated + ']&lt;=-'); if(!_.has(state, 'HealthColors') || state.HealthColors.schemaVersion !== schemaVersion) { log('&lt;' + ScriptName + ' Updating Schema to v' + schemaVersion + '&gt;'); state.HealthColors = {schemaVersion: schemaVersion}; state.HealthColors.version = version; } //CHECK STATE VALUES if(_.isUndefined(state.HealthColors.auraColorOn)) state.HealthColors.auraColorOn = true; //global on or off if(_.isUndefined(state.HealthColors.auraBar)) state.HealthColors.auraBar = "bar1"; //bar to use if(_.isUndefined(state.HealthColors.auraTint)) state.HealthColors.auraTint = false; //use tint instead? if(_.isUndefined(state.HealthColors.auraPercPC)) state.HealthColors.auraPercPC = 100; //precent to start showing PC if(_.isUndefined(state.HealthColors.auraPerc)) state.HealthColors.auraPerc = 100; //precent to start showing NPC //----------------- if(_.isUndefined(state.HealthColors.PCAura)) state.HealthColors.PCAura = true; //show players Health? if(_.isUndefined(state.HealthColors.NPCAura)) state.HealthColors.NPCAura = true; //show NPC Health? if(_.isUndefined(state.HealthColors.auraDeadPC)) state.HealthColors.auraDeadPC = true; //show dead X status PC if(_.isUndefined(state.HealthColors.auraDead)) state.HealthColors.auraDead = true; //show dead X status NPC //----------------- if(_.isUndefined(state.HealthColors.GM_PCNames)) state.HealthColors.GM_PCNames = "Yes"; //show GM PC names? if(_.isUndefined(state.HealthColors.PCNames)) state.HealthColors.PCNames = "Yes"; //show players PC Names? //----------------- if(_.isUndefined(state.HealthColors.GM_NPCNames)) state.HealthColors.GM_NPCNames = "Yes"; //show GM NPC names? if(_.isUndefined(state.HealthColors.NPCNames)) state.HealthColors.NPCNames = "Yes"; //show players NPC Names? //----------------- if(_.isUndefined(state.HealthColors.AuraSize)) state.HealthColors.AuraSize = 0.7; //set aura size? if(_.isUndefined(state.HealthColors.FX)) state.HealthColors.FX = true; //set FX ON/OFF? // Updated defaults for FX types (using DeathTracker FX names) if(_.isUndefined(state.HealthColors.HealFX)) state.HealthColors.HealFX = "glow-holy"; //set Heal FX type if(_.isUndefined(state.HealthColors.HurtFX)) state.HealthColors.HurtFX = "splatter-blood"; //set Hurt FX type if(_.isUndefined(state.HealthColors.auraDeadFX)) state.HealthColors.auraDeadFX = 'None'; //Sound FX Name //TokenMod CHECK if('undefined' !== typeof TokenMod &amp;&amp; TokenMod.ObserveTokenChange) TokenMod.ObserveTokenChange(handleToken); var FXHurt = findObjs({_type: "custfx",name: "-DefaultHurt"}, {caseInsensitive: true})[0]; var FXHeal = findObjs({_type: "custfx",name: "-DefaultHeal"}, {caseInsensitive: true})[0]; //DEFAULT FX CHECK if(!FXHurt) { GMW("Creating Default Hurt FX"); var Hurt = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 3, "lifeSpan": 25, "lifeSpanRandom": 5, "speed": 8, "speedRandom": 3, "gravity": {"x": 0.01,"y": 0.65}, "angle": 270, "angleRandom": 25, "emissionRate": 100, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0], }; createObj('custfx', {name: "-DefaultHurt",definition: Hurt}); } if(!FXHeal) { GMW("Creating Default Heal FX"); var Heal = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 15, "lifeSpan": 50, "lifeSpanRandom": 30, "speed": 0.5, "speedRandom": 2, "angle": 0, "angleRandom": 180, "emissionRate": 1000, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0], }; createObj('custfx', {name: "-DefaultHeal",definition: Heal}); } }, //WHISPER GM------------ GMW = function (text) { var DIV = "&lt;div 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%);"; var MSG = DIV + "'&gt;&lt;b&gt;"+text+"&lt;/b&gt;&lt;/div"; sendChat('HealthColors', "/w GM "+MSG); }, //OUTSIDE CALL------------ UpdateToken = function (obj, prev) { if (obj.get("type") === "graphic") handleToken(obj, prev); else GMW("Script sent non-Token to be updated!"); }, //REGISTER TRIGGERS------------ registerEventHandlers = function () { on('chat:message', handleInput); on("change:token", handleToken); on('add:token', function (t) { _.delay(() =&gt; { let token = getObj('graphic', t.id), prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }, 400); }); //register this script to SmartAoE to handle linked bar hp changes if('undefined' !== typeof SmartAoE &amp;&amp; SmartAoE.ObserveTokenChange){ SmartAoE.ObserveTokenChange(function(obj,prev){ handleToken(obj, prev, "NO"); }); }; }; //RETURN OUTSIDE FUNCTIONS------------ return { GMW: GMW, Update: UpdateToken, CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); //On Ready on('ready', function () { 'use strict'; HealthColors.GMW("API READY"); HealthColors.CheckInstall(); HealthColors.RegisterEventHandlers(); });
Surok said: I updated the AURA code to use the FX deathtracker uses. This version doesn't use aura 2 (I had removed aura 2 because I needed it for other auras for spells etc). Try it and let me know how it goes. /* global createObj TokenMod spawnFxWithDefinition getObj state playerIsGM sendChat _ findObjs log on*/ /* My Profile link: <a href="https://app.roll20.net/users/262130/dxwarlock" rel="nofollow">https://app.roll20.net/users/262130/dxwarlock</a> GIT link: <a href="https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors" rel="nofollow">https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors</a> Roll20Link: <a href="https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor" rel="nofollow">https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor</a> Version 1.6.1 - Feb 16 2025 - Updated FX effects: The damage/healing FX now use the simpler DeathTracker approach. (Damage FX uses the HurtFX type (default "splatter-blood") and Healing FX uses the HealFX type (default "glow-holy").) */ /*jshint bitwise: false*/ var HealthColors = HealthColors || (function () { 'use strict'; var version = '1.6.1', ScriptName = "HealthColors", schemaVersion = '1.0.3', Updated = "Feb 16 2025", /*------------------------ ON TOKEN CHANGE/CREATE ------------------------*/ handleToken = function (obj, prev, update) { //CHECK IF TRIGGERED------------ if(state.HealthColors.auraColorOn !== true || obj.get("layer") !== "objects") return; if(obj.get("represents") !== "" || (obj.get("represents") === "" &amp;&amp; state.HealthColors.OneOff === true)) { //**CHECK BARS------------// var barUsed = state.HealthColors.auraBar; var maxValue, curValue, prevValue; if(obj.get(barUsed + "_max") !== "" || obj.get(barUsed + "_value") !== "") { maxValue = parseInt(obj.get(barUsed + "_max"), 10); curValue = parseInt(obj.get(barUsed + "_value"), 10); prevValue = prev[barUsed + "_value"]; } if(isNaN(maxValue) || isNaN(curValue) || isNaN(prevValue)) return; //CALC PERCENTAGE------------ var percReal = Math.round((curValue / maxValue) * 100); var markerColor = PercentToHEX(percReal); //DEFINE VARIABLES--- var pColor = '#ffffff'; var GM = '', PC = ''; var IsTypeOn, PercentOn, ShowDead, UseAura; //**CHECK MONSTER OR PLAYER------------// var oCharacter = getObj('character', obj.get("_represents")); var type = (oCharacter === undefined || oCharacter.get("controlledby") === "") ? 'Monster' : 'Player'; var colortype = (state.HealthColors.auraTint) ? 'tint' : 'aura1'; //IF PLAYER------------ if(type == 'Player') { GM = state.HealthColors.GM_PCNames; PC = state.HealthColors.PCNames; IsTypeOn = state.HealthColors.PCAura; PercentOn = state.HealthColors.auraPercPC; ShowDead = state.HealthColors.auraDeadPC; var cBy = oCharacter.get('controlledby'); var player = getObj('player', cBy); pColor = '#000000'; if(player !== undefined) pColor = player.get('color'); } //IF MONSTER------------ else if(type == 'Monster') { GM = state.HealthColors.GM_NPCNames; PC = state.HealthColors.NPCNames; IsTypeOn = state.HealthColors.NPCAura; PercentOn = state.HealthColors.auraPerc; ShowDead = state.HealthColors.auraDead; } else return; //CHECK DISABLED AURA/TINT ATTRIB------------ if(oCharacter !== undefined) { UseAura = lookupUseColor(oCharacter); } //SET HEALTH COLOR---------- if(IsTypeOn &amp;&amp; UseAura !== "NO") { percReal = Math.min(percReal, 100); if(percReal &gt; PercentOn || curValue === 0) SetAuraNone(obj); else TokenSet(obj, state.HealthColors.AuraSize, markerColor, pColor, update); //SHOW DEAD---------- if(ShowDead === true) { if(curValue &gt; 0) obj.set("status_dead", false); else if(curValue &lt; 1) { var DeadSounds = state.HealthColors.auraDeadFX; if(DeadSounds !== "None" &amp;&amp; curValue != prevValue) PlayDeath(DeadSounds); obj.set("status_dead", true); SetAuraNone(obj); } } } else if((!IsTypeOn || UseAura === "NO") &amp;&amp; obj.get(colortype + '_color') === markerColor) SetAuraNone(obj); //SET SHOW NAMES------------ SetShowNames(GM, PC, obj); //**FX for Damage/Healing using DeathTracker's approach------------ if(curValue != prevValue &amp;&amp; prevValue != "" &amp;&amp; update !== "YES") { let left = parseInt(obj.get('left')), top = parseInt(obj.get('top')); if(state.HealthColors.FX === true) { if(curValue &lt; prevValue) { // Damage FX – using HurtFX type (default: splatter-blood) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HurtFX, obj.get('_pageid')); } else if(curValue &gt; prevValue) { // Healing FX – using HealFX type (default: glow-holy) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HealFX, obj.get('_pageid')); } } } } }, /*------------------------ CHAT MESSAGES ------------------------*/ handleInput = function (msg) { var msgFormula = msg.content.split(/\s+/); var command = msgFormula[0].toUpperCase(), UPPER =""; if(msg.type == "api" &amp;&amp; command.indexOf("!AURA") !== -1) { var OPTION = msgFormula[1] || "MENU"; if(!playerIsGM(msg.playerid)) { sendChat('HealthColors', "/w " + msg.who + " you must be a GM to use this command!"); return; } else { if(OPTION !== "MENU") GMW("UPDATING TOKENS..."); switch(OPTION.toUpperCase()) { case "MENU": break; case "ON": state.HealthColors.auraColorOn = !state.HealthColors.auraColorOn; break; case "BAR": state.HealthColors.auraBar = "bar" + msgFormula[2]; break; case "TINT": state.HealthColors.auraTint = !state.HealthColors.auraTint; break; case "PERC": state.HealthColors.auraPercPC = parseInt(msgFormula[2], 10); state.HealthColors.auraPerc = parseInt(msgFormula[3], 10); break; case "PC": state.HealthColors.PCAura = !state.HealthColors.PCAura; break; case "NPC": state.HealthColors.NPCAura = !state.HealthColors.NPCAura; break; case "GMNPC": state.HealthColors.GM_NPCNames = msgFormula[2]; break; case "GMPC": state.HealthColors.GM_PCNames = msgFormula[2]; break; case "PCNPC": state.HealthColors.NPCNames = msgFormula[2]; break; case "PCPC": state.HealthColors.PCNames = msgFormula[2]; break; case "DEAD": state.HealthColors.auraDead = !state.HealthColors.auraDead; break; case "DEADPC": state.HealthColors.auraDeadPC = !state.HealthColors.auraDeadPC; break; case "DEADFX": state.HealthColors.auraDeadFX = msgFormula[2]; break; case "SIZE": state.HealthColors.AuraSize = parseFloat(msgFormula[2]); break; case "ONEOFF": state.HealthColors.OneOff = !state.HealthColors.OneOff; break; case "FX": state.HealthColors.FX = !state.HealthColors.FX; break; case "HEAL": UPPER = msgFormula[2]; UPPER = UPPER.toLowerCase(); state.HealthColors.HealFX = UPPER; break; case "HURT": UPPER = msgFormula[2]; UPPER = UPPER.toLowerCase(); state.HealthColors.HurtFX = UPPER; break; case "RESET": delete state.HealthColors; GMW("STATE RESET"); checkInstall(); break; case "UPDATE": manUpdate(msg); return; } aurahelp(OPTION); } } }, /*------------------------ "FUNCTIONS" ------------------------*/ //SET TOKEN COLORS------------ TokenSet = function (obj, sizeSet, markerColor, pColor, update) { var Pageon = getObj("page", obj.get("_pageid")); var scale = Pageon.get("scale_number") / 10; if(state.HealthColors.auraTint === true) { if(obj.get('aura1_color') == markerColor &amp;&amp; update === "YES") { obj.set({'aura1_color': "transparent",}); } obj.set({'tint_color': markerColor,}); } else { if(obj.get('tint_color') == markerColor &amp;&amp; update === "YES") { obj.set({'tint_color': "transparent",}); } obj.set({ 'aura1_radius': sizeSet * scale * 1.8, 'aura1_color': markerColor, 'showplayers_aura1': true, }); } }, //REMOVE ALL------------ SetAuraNone = function (obj) { if(state.HealthColors.auraTint === true) obj.set({'tint_color': "transparent",}); else obj.set({'aura1_color': "transparent"}); }, //FORCE ALL TOKEN UPDATE------------ MenuForceUpdate = function(){ let i = 0; const start = new Date().getTime(); const barUsed = state.HealthColors.auraBar; const workQueue = findObjs({type: 'graphic',subtype: 'token',layer: 'objects'}) .filter((o)=&gt;o.get(barUsed + "_max") !== "" &amp;&amp; o.get(barUsed + "_value") !== ""); const drainQueue = ()=&gt;{ let t = workQueue.shift(); if(t){ const prev = JSON.parse(JSON.stringify(t)); handleToken( t, prev, 'YES'); setTimeout(drainQueue,0); } else { sendChat('Fixing Tokens',`/w gm Finished Fixing Tokens`); } }; sendChat('Fixing Tokens',`/w gm Fixing ${workQueue.length} Tokens`); drainQueue(); var end = new Date().getTime(); return "Tokens Processed: " + workQueue.length + "&lt;br&gt;Run time in ms: " + (end - start); }, SetShowNames = function(GM, PC, obj) { if(GM != 'Off' &amp;&amp; GM != '') { GM = (GM == "Yes") ? true : false; obj.set({'showname': GM}); } if(PC != 'Off' &amp;&amp; PC != '') { PC = (PC == "Yes") ? true : false; obj.set({'showplayers_name': PC}); } }, //MANUAL UPDATE------------ manUpdate = function(msg){ var selected = msg.selected; var allNames = ''; _.each(selected, function(obj) { var token = getObj('graphic', obj._id); var tName = token.get("name"); allNames = allNames.concat(tName+'&lt;br&gt;'); var prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }); GMW(allNames); }, //ATTRIBUTE CACHE------------ makeSmartAttrCache = function (attribute, options) { let cache = {}, defaultValue = options.default || 'YES', validator = options.validation || _.constant(true); on('change:attribute', function (attr) { if(attr.get('name') === attribute) { if(!validator(attr.get('current'))) { attr.set('current', defaultValue); } cache[attr.get('characterid')] = attr.get('current'); var tokens = findObjs({type: 'graphic'}).filter((o) =&gt; o.get('represents') === attr.get("characterid")); _.each(tokens, function (obj) { var prev = JSON.parse(JSON.stringify(obj)); handleToken(obj, prev, "YES"); }); } }); on('destroy:attribute', function (attr) { if(attr.get('name') === attribute) { delete cache[attr.get('characterid')]; } }); return function(character){ let attr = findObjs({type: 'attribute',name: attribute,characterid: character.id},{caseInsensitive:true})[0] || 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'); } return cache[character.id]; }; }, lookupUseBlood = makeSmartAttrCache('USEBLOOD',{ default: 'DEFAULT' }), lookupUseColor = makeSmartAttrCache('USECOLOR',{ default: 'YES', validation: (o)=&gt;o.match(/YES|NO/) }), //DEATH SOUND------------ PlayDeath = function (trackname) { var RandTrackName; if(trackname.indexOf(",") &gt; 0) { var tracklist = trackname.split(","); RandTrackName = tracklist[Math.floor(Math.random() * tracklist.length)]; } else RandTrackName = trackname; var track = findObjs({type: 'jukeboxtrack',title: RandTrackName})[0]; if(track) { track.set('playing', false); track.set('softstop', false); track.set('volume', 50); track.set('playing', true); } else { log(ScriptName + ": No track found named " + RandTrackName); } }, //PERC TO RGB------------ PercentToHEX = function (percent) { var HEX; if(percent &gt; 100) HEX = "#0000FF"; else { if(percent === 100) percent = 99; var r, g, b = 0; if(percent &lt; 50) { g = Math.floor(255 * (percent / 50)); r = 255; } else { g = 255; r = Math.floor(255 * ((50 - percent % 50) / 50)); } HEX = "#" + ((1 &lt;&lt; 24) + (r &lt;&lt; 16) + (g &lt;&lt; 8) + b).toString(16).slice(1); } return HEX; }, //HEX TO RGB------------ HEXtoRGB = function (hex) { let parts = (hex || '').match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); if(parts) { let rgb = _.chain(parts).rest().map((d) =&gt; parseInt(d, 16)).value(); rgb.push(1.0); return rgb; } return [0, 0, 0, 0.0]; }, //SPAWN FX------------ SpawnFX = function (Scale,HitSize,left,top,FX,pageid) { _.defaults(FX, { "maxParticles": 100, "duration": 100, "size": 100, "sizeRandom": 100, "lifeSpan": 100, "lifeSpanRandom": 100, "speed": 0, "speedRandom": 0, "angle": 0, "angleRandom": 0, "emissionRate": 100, "startColour": [255,255,255,1], "endColour": [0,0,0,1], "gravity": {"x": 0,"y": 0.0}, }); var newFX = { "maxParticles": FX.maxParticles * HitSize, "duration": FX.duration * HitSize, "size": FX.size * Scale / 2, "sizeRandom": FX.sizeRandom * Scale / 2, "lifeSpan": FX.lifeSpan, "lifeSpanRandom": FX.lifeSpanRandom, "speed": FX.speed * Scale, "speedRandom": FX.speedRandom * Scale, "angle": FX.angle, "angleRandom": FX.angleRandom, "emissionRate": FX.emissionRate * HitSize * 2, "startColour": FX.startColour, "endColour": FX.endColour, "gravity": {"x": FX.gravity.x * Scale,"y": FX.gravity.y * Scale}, }; spawnFxWithDefinition(left,top,newFX,pageid); }, //HELP MENU------------ aurahelp = function (OPTION) { var Update = ''; if(OPTION !== "MENU") Update = MenuForceUpdate(); var img = "background-image: -webkit-linear-gradient(left, #76ADD6 0%, #a7c7dc 100%);"; var tshadow = "-1px -1px #222, 1px -1px #222, -1px 1px #222, 1px 1px #222 , 2px 2px #222;"; // Original style for most buttons: var style = 'style="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;"'; // New style for FX Type buttons with a wider width: var fxStyle = 'style="padding-top: 1px; text-align:center; font-size: 9pt; width: 80px; height: 14px; border: 1px solid black; margin: 1px; background-color: #6FAEC7;border-radius: 4px; box-shadow: 1px 1px 1px #707070;"'; var off = "#A84D4D"; var disable = "#D6D6D6"; var HR = "&lt;hr style='background-color: #000000; margin: 5px; border-width:0;color: #000000;height: 1px;'/&gt;"; var FX = state.HealthColors.auraDeadFX.substring(0, 4); sendChat('HealthColors', "/w GM &lt;b&gt;&lt;br&gt;" + '&lt;div style="border-radius: 8px 8px 8px 8px; padding: 5px; font-size: 9pt; text-shadow: ' + tshadow + '; box-shadow: 3px 3px 1px #707070; ' + img + ' color:#FFF; border:2px solid black; text-align:right; vertical-align:middle;"&gt;' + '&lt;u&gt;&lt;big&gt;HealthColors Version: ' + version + '&lt;/u&gt;&lt;/big&gt;&lt;br&gt;' + HR + 'Is On: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraColorOn !== true ? off : "") + ';" href="!aura on"&gt;' + (state.HealthColors.auraColorOn !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Bar: &lt;a ' + style + '" href="!aura bar ?{Bar|1|2|3}"&gt;' + state.HealthColors.auraBar + '&lt;/a&gt;&lt;br&gt;' + 'Use Tint: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraTint !== true ? off : "") + ';" href="!aura tint"&gt;' + (state.HealthColors.auraTint !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Percentage(PC/NPC): &lt;a ' + style + '" href="!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}"&gt;' + state.HealthColors.auraPercPC + '/'+ state.HealthColors.auraPerc +'&lt;/a&gt;&lt;br&gt;' + HR + 'Show PC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.PCAura !== true ? off : "") + ';" href="!aura pc"&gt;' + (state.HealthColors.PCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show NPC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.NPCAura !== true ? off : "") + ';" href="!aura npc"&gt;' + (state.HealthColors.NPCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead PC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDeadPC !== true ? off : "") + ';" href="!aura deadPC"&gt;' + (state.HealthColors.auraDeadPC !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead NPC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDead !== true ? off : "") + ';" href="!aura dead"&gt;' + (state.HealthColors.auraDead !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + HR + 'GM Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_PCNames, off, disable) + ';" href="!aura gmpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_PCNames + '&lt;/a&gt;&lt;br&gt;' + 'GM Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_NPCNames, off, disable) + ';" href="!aura gmnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'PC Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.PCNames, off, disable) + ';" href="!aura pcpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.PCNames + '&lt;/a&gt;&lt;br&gt;' + 'PC Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.NPCNames, off, disable) + ';" href="!aura pcnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'Aura Size: &lt;a ' + style + '" href="!aura size ?{Size?|0.7}"&gt;' + state.HealthColors.AuraSize + '&lt;/a&gt;&lt;br&gt;' + 'One Offs: &lt;a ' + style + 'background-color:' + (state.HealthColors.OneOff !== true ? off : "") + ';" href="!aura ONEOFF"&gt;' + (state.HealthColors.OneOff !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'FX: &lt;a ' + style + 'background-color:' + (state.HealthColors.FX !== true ? off : "") + ';" href="!aura FX"&gt;' + (state.HealthColors.FX !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'HealFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HealFX + ';" href="!aura HEAL ?{FX Type?|glow-holy}"&gt;' + state.HealthColors.HealFX + '&lt;/a&gt;&lt;br&gt;' + 'HurtFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HurtFX + ';" href="!aura HURT ?{FX Type?|splatter-blood}"&gt;' + state.HealthColors.HurtFX + '&lt;/a&gt;&lt;br&gt;' + 'DeathSFX: &lt;a ' + style + '" href="!aura deadfx ?{Sound Name?|' + state.HealthColors.auraDeadFX + '}"&gt;' + FX + '&lt;/a&gt;&lt;br&gt;' + HR + Update + '&lt;/div&gt;'); }, //OFF BUTTON COLORS------------ ButtonColor = function (state, off, disable) { var color; if(state == "No") color = off; if(state == "Off") color = disable; return color; }, //CHECK INSTALL &amp; SET STATE------------ checkInstall = function () { log('-=&gt;' + ScriptName + ' v' + version + ' [Updated: ' + Updated + ']&lt;=-'); if(!_.has(state, 'HealthColors') || state.HealthColors.schemaVersion !== schemaVersion) { log('&lt;' + ScriptName + ' Updating Schema to v' + schemaVersion + '&gt;'); state.HealthColors = {schemaVersion: schemaVersion}; state.HealthColors.version = version; } //CHECK STATE VALUES if(_.isUndefined(state.HealthColors.auraColorOn)) state.HealthColors.auraColorOn = true; //global on or off if(_.isUndefined(state.HealthColors.auraBar)) state.HealthColors.auraBar = "bar1"; //bar to use if(_.isUndefined(state.HealthColors.auraTint)) state.HealthColors.auraTint = false; //use tint instead? if(_.isUndefined(state.HealthColors.auraPercPC)) state.HealthColors.auraPercPC = 100; //precent to start showing PC if(_.isUndefined(state.HealthColors.auraPerc)) state.HealthColors.auraPerc = 100; //precent to start showing NPC //----------------- if(_.isUndefined(state.HealthColors.PCAura)) state.HealthColors.PCAura = true; //show players Health? if(_.isUndefined(state.HealthColors.NPCAura)) state.HealthColors.NPCAura = true; //show NPC Health? if(_.isUndefined(state.HealthColors.auraDeadPC)) state.HealthColors.auraDeadPC = true; //show dead X status PC if(_.isUndefined(state.HealthColors.auraDead)) state.HealthColors.auraDead = true; //show dead X status NPC //----------------- if(_.isUndefined(state.HealthColors.GM_PCNames)) state.HealthColors.GM_PCNames = "Yes"; //show GM PC names? if(_.isUndefined(state.HealthColors.PCNames)) state.HealthColors.PCNames = "Yes"; //show players PC Names? //----------------- if(_.isUndefined(state.HealthColors.GM_NPCNames)) state.HealthColors.GM_NPCNames = "Yes"; //show GM NPC names? if(_.isUndefined(state.HealthColors.NPCNames)) state.HealthColors.NPCNames = "Yes"; //show players NPC Names? //----------------- if(_.isUndefined(state.HealthColors.AuraSize)) state.HealthColors.AuraSize = 0.7; //set aura size? if(_.isUndefined(state.HealthColors.FX)) state.HealthColors.FX = true; //set FX ON/OFF? // Updated defaults for FX types (using DeathTracker FX names) if(_.isUndefined(state.HealthColors.HealFX)) state.HealthColors.HealFX = "glow-holy"; //set Heal FX type if(_.isUndefined(state.HealthColors.HurtFX)) state.HealthColors.HurtFX = "splatter-blood"; //set Hurt FX type if(_.isUndefined(state.HealthColors.auraDeadFX)) state.HealthColors.auraDeadFX = 'None'; //Sound FX Name //TokenMod CHECK if('undefined' !== typeof TokenMod &amp;&amp; TokenMod.ObserveTokenChange) TokenMod.ObserveTokenChange(handleToken); var FXHurt = findObjs({_type: "custfx",name: "-DefaultHurt"}, {caseInsensitive: true})[0]; var FXHeal = findObjs({_type: "custfx",name: "-DefaultHeal"}, {caseInsensitive: true})[0]; //DEFAULT FX CHECK if(!FXHurt) { GMW("Creating Default Hurt FX"); var Hurt = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 3, "lifeSpan": 25, "lifeSpanRandom": 5, "speed": 8, "speedRandom": 3, "gravity": {"x": 0.01,"y": 0.65}, "angle": 270, "angleRandom": 25, "emissionRate": 100, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0], }; createObj('custfx', {name: "-DefaultHurt",definition: Hurt}); } if(!FXHeal) { GMW("Creating Default Heal FX"); var Heal = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 15, "lifeSpan": 50, "lifeSpanRandom": 30, "speed": 0.5, "speedRandom": 2, "angle": 0, "angleRandom": 180, "emissionRate": 1000, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0], }; createObj('custfx', {name: "-DefaultHeal",definition: Heal}); } }, //WHISPER GM------------ GMW = function (text) { var DIV = "&lt;div 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%);"; var MSG = DIV + "'&gt;&lt;b&gt;"+text+"&lt;/b&gt;&lt;/div"; sendChat('HealthColors', "/w GM "+MSG); }, //OUTSIDE CALL------------ UpdateToken = function (obj, prev) { if (obj.get("type") === "graphic") handleToken(obj, prev); else GMW("Script sent non-Token to be updated!"); }, //REGISTER TRIGGERS------------ registerEventHandlers = function () { on('chat:message', handleInput); on("change:token", handleToken); on('add:token', function (t) { _.delay(() =&gt; { let token = getObj('graphic', t.id), prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }, 400); }); //register this script to SmartAoE to handle linked bar hp changes if('undefined' !== typeof SmartAoE &amp;&amp; SmartAoE.ObserveTokenChange){ SmartAoE.ObserveTokenChange(function(obj,prev){ handleToken(obj, prev, "NO"); }); }; }; //RETURN OUTSIDE FUNCTIONS------------ return { GMW: GMW, Update: UpdateToken, CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); //On Ready on('ready', function () { 'use strict'; HealthColors.GMW("API READY"); HealthColors.CheckInstall(); HealthColors.RegisterEventHandlers(); }); I have been having the same issue as Crossfade. This script works perfectly! Thank you for sharing it!
1741281616

Edited 1741281668
Surok said: I've been using Deathtracker instead. It's has functional fx and handles hp auras like&nbsp;Aura/Tint HealthColors. Thank you so much for your fix! Seems to be working great. I just have one question: How do I add a custom FX for heal/hurt? I tried it with "custom-defaulthurt" into HurtFX similar to how the standard is "splatter-blood" but that does not work.
Crossfade said: Surok said: I've been using Deathtracker instead. It's has functional fx and handles hp auras like&nbsp;Aura/Tint HealthColors. Thank you so much for your fix! Seems to be working great. I just have one question: How do I add a custom FX for heal/hurt? I tried it with "custom-defaulthurt" into HurtFX similar to how the standard is "splatter-blood" but that does not work. OK so it turned out to be not so simple. Apparently you can't just go by the custom fx name, you have to get it's unique id which I added a function to the API for you to get. Click the new button Custom FX ids: It should give you a list of your custom fxs You can copy paste that id into the heal or hurt FX button prompt in the main menu or use the following macros !aura HURT "id here" !aura HEAL "id here" For example:&nbsp; !aura HURT -NYY2rPGAzI-qN3XHDri Here's the updated code, let me know if you have any other issues: /* global createObj TokenMod spawnFxWithDefinition getObj state playerIsGM sendChat _ findObjs log on*/ /* My Profile link: <a href="https://app.roll20.net/users/262130/dxwarlock" rel="nofollow">https://app.roll20.net/users/262130/dxwarlock</a> GIT link: <a href="https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors" rel="nofollow">https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors</a> Roll20Link: <a href="https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor" rel="nofollow">https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor</a> Version 1.7.1 - Mar 6 2025 - Updated FX effects: The damage/healing FX now use the simpler DeathTracker approach. (Damage FX uses the HurtFX type (default "splatter-blood") and Healing FX uses the HealFX type (default "glow-holy").) - Added a new command/button ("LISTFX") that displays a graphical menu listing custom FX objects with their IDs. */ /*jshint bitwise: false*/ var HealthColors = HealthColors || (function () { 'use strict'; var version = '1.7.1', ScriptName = "HealthColors", schemaVersion = '1.0.3', Updated = "Feb 16 2025", /*------------------------ ON TOKEN CHANGE/CREATE ------------------------*/ handleToken = function (obj, prev, update) { //CHECK IF TRIGGERED------------ if(state.HealthColors.auraColorOn !== true || obj.get("layer") !== "objects") return; if(obj.get("represents") !== "" || (obj.get("represents") === "" &amp;&amp; state.HealthColors.OneOff === true)) { //**CHECK BARS------------// var barUsed = state.HealthColors.auraBar; var maxValue, curValue, prevValue; if(obj.get(barUsed + "_max") !== "" || obj.get(barUsed + "_value") !== "") { maxValue = parseInt(obj.get(barUsed + "_max"), 10); curValue = parseInt(obj.get(barUsed + "_value"), 10); prevValue = prev[barUsed + "_value"]; } if(isNaN(maxValue) || isNaN(curValue) || isNaN(prevValue)) return; //CALC PERCENTAGE------------ var percReal = Math.round((curValue / maxValue) * 100); var markerColor = PercentToHEX(percReal); //DEFINE VARIABLES--- var pColor = '#ffffff'; var GM = '', PC = ''; var IsTypeOn, PercentOn, ShowDead, UseAura; //**CHECK MONSTER OR PLAYER------------// var oCharacter = getObj('character', obj.get("_represents")); var type = (oCharacter === undefined || oCharacter.get("controlledby") === "") ? 'Monster' : 'Player'; var colortype = (state.HealthColors.auraTint) ? 'tint' : 'aura1'; //IF PLAYER------------ if(type == 'Player') { GM = state.HealthColors.GM_PCNames; PC = state.HealthColors.PCNames; IsTypeOn = state.HealthColors.PCAura; PercentOn = state.HealthColors.auraPercPC; ShowDead = state.HealthColors.auraDeadPC; var cBy = oCharacter.get('controlledby'); var player = getObj('player', cBy); pColor = '#000000'; if(player !== undefined) pColor = player.get('color'); } //IF MONSTER------------ else if(type == 'Monster') { GM = state.HealthColors.GM_NPCNames; PC = state.HealthColors.NPCNames; IsTypeOn = state.HealthColors.NPCAura; PercentOn = state.HealthColors.auraPerc; ShowDead = state.HealthColors.auraDead; } else return; //CHECK DISABLED AURA/TINT ATTRIB------------ if(oCharacter !== undefined) { UseAura = lookupUseColor(oCharacter); } //SET HEALTH COLOR---------- if(IsTypeOn &amp;&amp; UseAura !== "NO") { percReal = Math.min(percReal, 100); if(percReal &gt; PercentOn || curValue === 0) SetAuraNone(obj); else TokenSet(obj, state.HealthColors.AuraSize, markerColor, pColor, update); //SHOW DEAD---------- if(ShowDead === true) { if(curValue &gt; 0) obj.set("status_dead", false); else if(curValue &lt; 1) { var DeadSounds = state.HealthColors.auraDeadFX; if(DeadSounds !== "None" &amp;&amp; curValue != prevValue) PlayDeath(DeadSounds); obj.set("status_dead", true); SetAuraNone(obj); } } } else if((!IsTypeOn || UseAura === "NO") &amp;&amp; obj.get(colortype + '_color') === markerColor) SetAuraNone(obj); //SET SHOW NAMES------------ SetShowNames(GM, PC, obj); //**FX for Damage/Healing using DeathTracker's approach------------ if(curValue != prevValue &amp;&amp; prevValue != "" &amp;&amp; update !== "YES") { let left = parseInt(obj.get('left')), top = parseInt(obj.get('top')); if(state.HealthColors.FX === true) { if(curValue &lt; prevValue) { // Damage FX – using HurtFX type (default: splatter-blood) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HurtFX, obj.get('_pageid')); } else if(curValue &gt; prevValue) { // Healing FX – using HealFX type (default: glow-holy) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HealFX, obj.get('_pageid')); } } } } }, /*------------------------ CHAT MESSAGES ------------------------*/ handleInput = function (msg) { var msgFormula = msg.content.split(/\s+/); var command = msgFormula[0].toUpperCase(), UPPER =""; if(msg.type == "api" &amp;&amp; command.indexOf("!AURA") !== -1) { var OPTION = msgFormula[1] || "MENU"; if(!playerIsGM(msg.playerid)) { sendChat('HealthColors', "/w " + msg.who + " you must be a GM to use this command!"); return; } else { if(OPTION !== "MENU") GMW("UPDATING TOKENS..."); switch(OPTION.toUpperCase()) { case "MENU": break; case "ON": state.HealthColors.auraColorOn = !state.HealthColors.auraColorOn; break; case "BAR": state.HealthColors.auraBar = "bar" + msgFormula[2]; break; case "TINT": state.HealthColors.auraTint = !state.HealthColors.auraTint; break; case "PERC": state.HealthColors.auraPercPC = parseInt(msgFormula[2], 10); state.HealthColors.auraPerc = parseInt(msgFormula[3], 10); break; case "PC": state.HealthColors.PCAura = !state.HealthColors.PCAura; break; case "NPC": state.HealthColors.NPCAura = !state.HealthColors.NPCAura; break; case "GMNPC": state.HealthColors.GM_NPCNames = msgFormula[2]; break; case "GMPC": state.HealthColors.GM_PCNames = msgFormula[2]; break; case "PCNPC": state.HealthColors.NPCNames = msgFormula[2]; break; case "PCPC": state.HealthColors.PCNames = msgFormula[2]; break; case "DEAD": state.HealthColors.auraDead = !state.HealthColors.auraDead; break; case "DEADPC": state.HealthColors.auraDeadPC = !state.HealthColors.auraDeadPC; break; case "DEADFX": state.HealthColors.auraDeadFX = msgFormula[2]; break; case "SIZE": state.HealthColors.AuraSize = parseFloat(msgFormula[2]); break; case "ONEOFF": state.HealthColors.OneOff = !state.HealthColors.OneOff; break; case "FX": state.HealthColors.FX = !state.HealthColors.FX; break; case "HEAL": UPPER = msgFormula[2]; state.HealthColors.HealFX = UPPER; break; case "HURT": UPPER = msgFormula[2]; state.HealthColors.HurtFX = UPPER; break; case "LISTFX": listCustomFX(); break; case "RESET": delete state.HealthColors; GMW("STATE RESET"); checkInstall(); break; case "UPDATE": manUpdate(msg); return; } aurahelp(OPTION); } } }, // New function: List Custom FX IDs in a formatted menu listCustomFX = function() { var allCustFX = findObjs({ _type: "custfx" }); var output = "&lt;div style='border: 1px solid #000; background-color: #f2f2f2; padding: 5px; border-radius: 4px; max-width: 400px;'&gt;"; output += "&lt;strong&gt;Custom FX IDs:&lt;/strong&gt;&lt;br&gt;&lt;ul style='list-style: none; padding: 0; margin: 0;'&gt;"; if(allCustFX.length &gt; 0) { allCustFX.forEach(function(custfx) { var fxName = custfx.get("name") || "[Unnamed]"; output += "&lt;li style='padding: 2px 0;'&gt;&lt;span style='font-weight:bold;'&gt;" + fxName + ":&lt;/span&gt; " + custfx.get("_id") + "&lt;/li&gt;"; }); } else { output += "&lt;li&gt;No custom FX objects found.&lt;/li&gt;"; } output += "&lt;/ul&gt;&lt;/div&gt;"; sendChat('HealthColors', "/w gm " + output); }, //SET TOKEN COLORS------------ TokenSet = function (obj, sizeSet, markerColor, pColor, update) { var Pageon = getObj("page", obj.get("_pageid")); var scale = Pageon.get("scale_number") / 10; if(state.HealthColors.auraTint === true) { if(obj.get('aura1_color') == markerColor &amp;&amp; update === "YES") { obj.set({'aura1_color': "transparent"}); } obj.set({'tint_color': markerColor}); } else { if(obj.get('tint_color') == markerColor &amp;&amp; update === "YES") { obj.set({'tint_color': "transparent"}); } obj.set({ 'aura1_radius': sizeSet * scale * 1.8, 'aura1_color': markerColor, 'showplayers_aura1': true }); } }, //REMOVE ALL------------ SetAuraNone = function (obj) { if(state.HealthColors.auraTint === true) obj.set({'tint_color': "transparent"}); else obj.set({'aura1_color': "transparent"}); }, //FORCE ALL TOKEN UPDATE------------ MenuForceUpdate = function(){ let i = 0; const start = new Date().getTime(); const barUsed = state.HealthColors.auraBar; const workQueue = findObjs({type: 'graphic', subtype: 'token', layer: 'objects'}) .filter((o) =&gt; o.get(barUsed + "_max") !== "" &amp;&amp; o.get(barUsed + "_value") !== ""); const drainQueue = () =&gt; { let t = workQueue.shift(); if(t){ const prev = JSON.parse(JSON.stringify(t)); handleToken(t, prev, 'YES'); setTimeout(drainQueue, 0); } else { sendChat('Fixing Tokens', `/w gm Finished Fixing Tokens`); } }; sendChat('Fixing Tokens', `/w gm Fixing ${workQueue.length} Tokens`); drainQueue(); var end = new Date().getTime(); return "Tokens Processed: " + workQueue.length + "&lt;br&gt;Run time in ms: " + (end - start); }, SetShowNames = function(GM, PC, obj) { if(GM != 'Off' &amp;&amp; GM != '') { GM = (GM == "Yes") ? true : false; obj.set({'showname': GM}); } if(PC != 'Off' &amp;&amp; PC != '') { PC = (PC == "Yes") ? true : false; obj.set({'showplayers_name': PC}); } }, //MANUAL UPDATE------------ manUpdate = function (msg) { var selected = msg.selected; var allNames = ''; _.each(selected, function (obj) { var token = getObj('graphic', obj._id); var tName = token.get("name"); allNames = allNames.concat(tName + '&lt;br&gt;'); var prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }); GMW(allNames); }, //ATTRIBUTE CACHE------------ makeSmartAttrCache = function (attribute, options) { let cache = {}, defaultValue = options.default || 'YES', validator = options.validation || _.constant(true); on('change:attribute', function (attr) { if(attr.get('name') === attribute) { if(!validator(attr.get('current'))) { attr.set('current', defaultValue); } cache[attr.get('characterid')] = attr.get('current'); var tokens = findObjs({type: 'graphic'}).filter((o) =&gt; o.get('represents') === attr.get("characterid")); _.each(tokens, function (obj) { var prev = JSON.parse(JSON.stringify(obj)); handleToken(obj, prev, "YES"); }); } }); on('destroy:attribute', function (attr) { if(attr.get('name') === attribute) { delete cache[attr.get('characterid')]; } }); return function(character){ let attr = findObjs({type: 'attribute', name: attribute, characterid: character.id}, {caseInsensitive:true})[0] || 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'); } return cache[character.id]; }; }, lookupUseBlood = makeSmartAttrCache('USEBLOOD', { default: 'DEFAULT' }), lookupUseColor = makeSmartAttrCache('USECOLOR', { default: 'YES', validation: (o) =&gt; o.match(/YES|NO/) }), //DEATH SOUND------------ PlayDeath = function (trackname) { var RandTrackName; if(trackname.indexOf(",") &gt; 0) { var tracklist = trackname.split(","); RandTrackName = tracklist[Math.floor(Math.random() * tracklist.length)]; } else RandTrackName = trackname; var track = findObjs({type: 'jukeboxtrack', title: RandTrackName})[0]; if(track) { track.set('playing', false); track.set('softstop', false); track.set('volume', 50); track.set('playing', true); } else { log(ScriptName + ": No track found named " + RandTrackName); } }, //PERC TO RGB------------ PercentToHEX = function (percent) { var HEX; if(percent &gt; 100) HEX = "#0000FF"; else { if(percent === 100) percent = 99; var r, g, b = 0; if(percent &lt; 50) { g = Math.floor(255 * (percent / 50)); r = 255; } else { g = 255; r = Math.floor(255 * ((50 - percent % 50) / 50)); } HEX = "#" + ((1 &lt;&lt; 24) + (r &lt;&lt; 16) + (g &lt;&lt; 8) + b).toString(16).slice(1); } return HEX; }, //HEX TO RGB------------ HEXtoRGB = function (hex) { let parts = (hex || '').match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); if(parts) { let rgb = _.chain(parts).rest().map((d) =&gt; parseInt(d, 16)).value(); rgb.push(1.0); return rgb; } return [0, 0, 0, 0.0]; }, //SPAWN FX------------ SpawnFX = function (Scale, HitSize, left, top, FX, pageid) { _.defaults(FX, { "maxParticles": 100, "duration": 100, "size": 100, "sizeRandom": 100, "lifeSpan": 100, "lifeSpanRandom": 100, "speed": 0, "speedRandom": 0, "angle": 0, "angleRandom": 0, "emissionRate": 100, "startColour": [255, 255, 255, 1], "endColour": [0, 0, 0, 1], "gravity": {"x": 0, "y": 0.0} }); var newFX = { "maxParticles": FX.maxParticles * HitSize, "duration": FX.duration * HitSize, "size": FX.size * Scale / 2, "sizeRandom": FX.sizeRandom * Scale / 2, "lifeSpan": FX.lifeSpan, "lifeSpanRandom": FX.lifeSpanRandom, "speed": FX.speed * Scale, "speedRandom": FX.speedRandom * Scale, "angle": FX.angle, "angleRandom": FX.angleRandom, "emissionRate": FX.emissionRate * HitSize * 2, "startColour": FX.startColour, "endColour": FX.endColour, "gravity": {"x": FX.gravity.x * Scale, "y": FX.gravity.y * Scale} }; spawnFxWithDefinition(left, top, newFX, pageid); }, //HELP MENU------------ aurahelp = function (OPTION) { var Update = ''; if(OPTION !== "MENU") Update = MenuForceUpdate(); var img = "background-image: -webkit-linear-gradient(left, #76ADD6 0%, #a7c7dc 100%);"; var tshadow = "-1px -1px #222, 1px -1px #222, -1px 1px #222, 1px 1px #222 , 2px 2px #222;"; // Base style for buttons with fixed width (for most items) var style = 'style="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;"'; // FX type buttons use a wider style var fxStyle = 'style="padding-top: 1px; text-align:center; font-size: 9pt; width: 80px; height: 14px; border: 1px solid black; margin: 1px; background-color: #6FAEC7; border-radius: 4px; box-shadow: 1px 1px 1px #707070;"'; // For buttons that should adjust to the text length, we use auto width. var autoStyle = 'style="padding: 1px 3px; text-align:center; font-size: 9pt; border: 1px solid black; margin: 1px; background-color: #6FAEC7; border-radius: 4px; box-shadow: 1px 1px 1px #707070; display:inline-block; width:auto;"'; var off = "#A84D4D"; var disable = "#D6D6D6"; var HR = "&lt;hr style='background-color: #000000; margin: 5px; border-width:0; color: #000000; height: 1px;'/&gt;"; var FX = state.HealthColors.auraDeadFX.substring(0, 4); // New button renamed "Custom FX ids" using autoStyle so it adjusts to the text length. var listFXButton = '&lt;a ' + autoStyle + ' href="!aura LISTFX"&gt;Custom FX ids&lt;/a&gt;&lt;br&gt;'; sendChat('HealthColors', "/w gm &lt;b&gt;&lt;br&gt;" + '&lt;div style="border-radius: 8px; padding: 5px; font-size: 9pt; text-shadow: ' + tshadow + '; box-shadow: 3px 3px 1px #707070; ' + img + ' color:#FFF; border:2px solid black; text-align:right; vertical-align:middle;"&gt;' + '&lt;u&gt;&lt;big&gt;HealthColors Version: ' + version + '&lt;/u&gt;&lt;/big&gt;&lt;br&gt;' + HR + 'Is On: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraColorOn !== true ? off : "") + ';" href="!aura on"&gt;' + (state.HealthColors.auraColorOn !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Bar: &lt;a ' + style + ' href="!aura bar ?{Bar|1|2|3}"&gt;' + state.HealthColors.auraBar + '&lt;/a&gt;&lt;br&gt;' + 'Use Tint: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraTint !== true ? off : "") + ';" href="!aura tint"&gt;' + (state.HealthColors.auraTint !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Percentage(PC/NPC): &lt;a ' + style + ' href="!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}"&gt;' + state.HealthColors.auraPercPC + '/' + state.HealthColors.auraPerc + '&lt;/a&gt;&lt;br&gt;' + HR + 'Show PC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.PCAura !== true ? off : "") + ';" href="!aura pc"&gt;' + (state.HealthColors.PCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show NPC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.NPCAura !== true ? off : "") + ';" href="!aura npc"&gt;' + (state.HealthColors.NPCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead PC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDeadPC !== true ? off : "") + ';" href="!aura deadPC"&gt;' + (state.HealthColors.auraDeadPC !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead NPC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDead !== true ? off : "") + ';" href="!aura dead"&gt;' + (state.HealthColors.auraDead !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + HR + 'GM Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_PCNames, off, disable) + ';" href="!aura gmpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_PCNames + '&lt;/a&gt;&lt;br&gt;' + 'GM Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_NPCNames, off, disable) + ';" href="!aura gmnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'PC Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.PCNames, off, disable) + ';" href="!aura pcpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.PCNames + '&lt;/a&gt;&lt;br&gt;' + 'PC Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.NPCNames, off, disable) + ';" href="!aura pcnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'Aura Size: &lt;a ' + style + ' href="!aura size ?{Size?|0.7}"&gt;' + state.HealthColors.AuraSize + '&lt;/a&gt;&lt;br&gt;' + 'One Offs: &lt;a ' + style + 'background-color:' + (state.HealthColors.OneOff !== true ? off : "") + ';" href="!aura ONEOFF"&gt;' + (state.HealthColors.OneOff !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'FX: &lt;a ' + style + 'background-color:' + (state.HealthColors.FX !== true ? off : "") + ';" href="!aura FX"&gt;' + (state.HealthColors.FX !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'HealFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HealFX + ';" href="!aura HEAL ?{FX Type?|glow-holy}"&gt;' + state.HealthColors.HealFX + '&lt;/a&gt;&lt;br&gt;' + 'HurtFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HurtFX + ';" href="!aura HURT ?{FX Type?|splatter-blood}"&gt;' + state.HealthColors.HurtFX + '&lt;/a&gt;&lt;br&gt;' + 'DeathSFX: &lt;a ' + style + ' href="!aura deadfx ?{Sound Name?|' + state.HealthColors.auraDeadFX + '}"&gt;' + FX + '&lt;/a&gt;&lt;br&gt;' + HR + listFXButton + Update + '&lt;/div&gt;'); }, //OFF BUTTON COLORS------------ ButtonColor = function (state, off, disable) { var color; if(state == "No") color = off; if(state == "Off") color = disable; return color; }, //CHECK INSTALL &amp; SET STATE------------ checkInstall = function () { log('-=&gt;' + ScriptName + ' v' + version + ' [Updated: ' + Updated + ']&lt;=-'); if(!_.has(state, 'HealthColors') || state.HealthColors.schemaVersion !== schemaVersion) { log('&lt;' + ScriptName + ' Updating Schema to v' + schemaVersion + '&gt;'); state.HealthColors = {schemaVersion: schemaVersion}; state.HealthColors.version = version; } //CHECK STATE VALUES if(_.isUndefined(state.HealthColors.auraColorOn)) state.HealthColors.auraColorOn = true; //global on or off if(_.isUndefined(state.HealthColors.auraBar)) state.HealthColors.auraBar = "bar1"; //bar to use if(_.isUndefined(state.HealthColors.auraTint)) state.HealthColors.auraTint = false; //use tint instead? if(_.isUndefined(state.HealthColors.auraPercPC)) state.HealthColors.auraPercPC = 100; //precent to start showing PC if(_.isUndefined(state.HealthColors.auraPerc)) state.HealthColors.auraPerc = 100; //precent to start showing NPC //----------------- if(_.isUndefined(state.HealthColors.PCAura)) state.HealthColors.PCAura = true; //show players Health? if(_.isUndefined(state.HealthColors.NPCAura)) state.HealthColors.NPCAura = true; //show NPC Health? if(_.isUndefined(state.HealthColors.auraDeadPC)) state.HealthColors.auraDeadPC = true; //show dead X status PC if(_.isUndefined(state.HealthColors.auraDead)) state.HealthColors.auraDead = true; //show dead X status NPC //----------------- if(_.isUndefined(state.HealthColors.GM_PCNames)) state.HealthColors.GM_PCNames = "Yes"; //show GM PC names? if(_.isUndefined(state.HealthColors.PCNames)) state.HealthColors.PCNames = "Yes"; //show players PC Names? //----------------- if(_.isUndefined(state.HealthColors.GM_NPCNames)) state.HealthColors.GM_NPCNames = "Yes"; //show GM NPC names? if(_.isUndefined(state.HealthColors.NPCNames)) state.HealthColors.NPCNames = "Yes"; //show players NPC Names? //----------------- if(_.isUndefined(state.HealthColors.AuraSize)) state.HealthColors.AuraSize = 0.7; //set aura size? if(_.isUndefined(state.HealthColors.FX)) state.HealthColors.FX = true; //set FX ON/OFF? // Updated defaults for FX types (using DeathTracker FX names) if(_.isUndefined(state.HealthColors.HealFX)) state.HealthColors.HealFX = "glow-holy"; //set Heal FX type if(_.isUndefined(state.HealthColors.HurtFX)) state.HealthColors.HurtFX = "splatter-blood"; //set Hurt FX type if(_.isUndefined(state.HealthColors.auraDeadFX)) state.HealthColors.auraDeadFX = 'None'; //Sound FX Name //TokenMod CHECK if('undefined' !== typeof TokenMod &amp;&amp; TokenMod.ObserveTokenChange) TokenMod.ObserveTokenChange(handleToken); var FXHurt = findObjs({_type: "custfx", name: "-DefaultHurt"}, {caseInsensitive: true})[0]; var FXHeal = findObjs({_type: "custfx", name: "-DefaultHeal"}, {caseInsensitive: true})[0]; //DEFAULT FX CHECK if(!FXHurt) { GMW("Creating Default Hurt FX"); var Hurt = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 3, "lifeSpan": 25, "lifeSpanRandom": 5, "speed": 8, "speedRandom": 3, "gravity": {"x": 0.01, "y": 0.65}, "angle": 270, "angleRandom": 25, "emissionRate": 100, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0] }; createObj('custfx', {name: "-DefaultHurt", definition: Hurt}); } if(!FXHeal) { GMW("Creating Default Heal FX"); var Heal = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 15, "lifeSpan": 50, "lifeSpanRandom": 30, "speed": 0.5, "speedRandom": 2, "angle": 0, "angleRandom": 180, "emissionRate": 1000, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0] }; createObj('custfx', {name: "-DefaultHeal", definition: Heal}); } }, //WHISPER GM------------ GMW = function (text) { var DIV = "&lt;div 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%);"; var MSG = DIV + "'&gt;&lt;b&gt;" + text + "&lt;/b&gt;&lt;/div"; sendChat('HealthColors', "/w GM " + MSG); }, //OUTSIDE CALL------------ UpdateToken = function (obj, prev) { if (obj.get("type") === "graphic") handleToken(obj, prev); else GMW("Script sent non-Token to be updated!"); }, //REGISTER TRIGGERS------------ registerEventHandlers = function () { on('chat:message', handleInput); on("change:token", handleToken); on('add:token', function (t) { _.delay(() =&gt; { let token = getObj('graphic', t.id), prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }, 400); }); //register this script to SmartAoE to handle linked bar hp changes if('undefined' !== typeof SmartAoE &amp;&amp; SmartAoE.ObserveTokenChange){ SmartAoE.ObserveTokenChange(function(obj, prev){ handleToken(obj, prev, "NO"); }); }; }; //RETURN OUTSIDE FUNCTIONS------------ return { GMW: GMW, Update: UpdateToken, CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); //On Ready on('ready', function () { 'use strict'; HealthColors.GMW("API READY"); HealthColors.CheckInstall(); HealthColors.RegisterEventHandlers(); });
Surok said: Crossfade said: Surok said: I've been using Deathtracker instead. It's has functional fx and handles hp auras like&nbsp;Aura/Tint HealthColors. Thank you so much for your fix! Seems to be working great. I just have one question: How do I add a custom FX for heal/hurt? I tried it with "custom-defaulthurt" into HurtFX similar to how the standard is "splatter-blood" but that does not work. OK so it turned out to be not so simple. Apparently you can't just go by the custom fx name, you have to get it's unique id which I added a function to the API for you to get. Click the new button Custom FX ids: It should give you a list of your custom fxs You can copy paste that id into the heal or hurt FX button prompt in the main menu or use the following macros !aura HURT "id here" !aura HEAL "id here" For example:&nbsp; !aura HURT -NYY2rPGAzI-qN3XHDri Here's the updated code, let me know if you have any other issues: Thank you! I really appreciate your fast and insightful help. Worked as intended!
Surok said: Crossfade said: Surok said: I've been using Deathtracker instead. It's has functional fx and handles hp auras like&nbsp;Aura/Tint HealthColors. Thank you so much for your fix! Seems to be working great. I just have one question: How do I add a custom FX for heal/hurt? I tried it with "custom-defaulthurt" into HurtFX similar to how the standard is "splatter-blood" but that does not work. OK so it turned out to be not so simple. Apparently you can't just go by the custom fx name, you have to get it's unique id which I added a function to the API for you to get. Click the new button Custom FX ids: It should give you a list of your custom fxs You can copy paste that id into the heal or hurt FX button prompt in the main menu or use the following macros !aura HURT "id here" !aura HEAL "id here" For example:&nbsp; !aura HURT -NYY2rPGAzI-qN3XHDri Here's the updated code, let me know if you have any other issues: /* global createObj TokenMod spawnFxWithDefinition getObj state playerIsGM sendChat _ findObjs log on*/ /* My Profile link: <a href="https://app.roll20.net/users/262130/dxwarlock" rel="nofollow">https://app.roll20.net/users/262130/dxwarlock</a> GIT link: <a href="https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors" rel="nofollow">https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors</a> Roll20Link: <a href="https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor" rel="nofollow">https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor</a> Version 1.7.1 - Mar 6 2025 - Updated FX effects: The damage/healing FX now use the simpler DeathTracker approach. (Damage FX uses the HurtFX type (default "splatter-blood") and Healing FX uses the HealFX type (default "glow-holy").) - Added a new command/button ("LISTFX") that displays a graphical menu listing custom FX objects with their IDs. */ /*jshint bitwise: false*/ var HealthColors = HealthColors || (function () { 'use strict'; var version = '1.7.1', ScriptName = "HealthColors", schemaVersion = '1.0.3', Updated = "Feb 16 2025", /*------------------------ ON TOKEN CHANGE/CREATE ------------------------*/ handleToken = function (obj, prev, update) { //CHECK IF TRIGGERED------------ if(state.HealthColors.auraColorOn !== true || obj.get("layer") !== "objects") return; if(obj.get("represents") !== "" || (obj.get("represents") === "" &amp;&amp; state.HealthColors.OneOff === true)) { //**CHECK BARS------------// var barUsed = state.HealthColors.auraBar; var maxValue, curValue, prevValue; if(obj.get(barUsed + "_max") !== "" || obj.get(barUsed + "_value") !== "") { maxValue = parseInt(obj.get(barUsed + "_max"), 10); curValue = parseInt(obj.get(barUsed + "_value"), 10); prevValue = prev[barUsed + "_value"]; } if(isNaN(maxValue) || isNaN(curValue) || isNaN(prevValue)) return; //CALC PERCENTAGE------------ var percReal = Math.round((curValue / maxValue) * 100); var markerColor = PercentToHEX(percReal); //DEFINE VARIABLES--- var pColor = '#ffffff'; var GM = '', PC = ''; var IsTypeOn, PercentOn, ShowDead, UseAura; //**CHECK MONSTER OR PLAYER------------// var oCharacter = getObj('character', obj.get("_represents")); var type = (oCharacter === undefined || oCharacter.get("controlledby") === "") ? 'Monster' : 'Player'; var colortype = (state.HealthColors.auraTint) ? 'tint' : 'aura1'; //IF PLAYER------------ if(type == 'Player') { GM = state.HealthColors.GM_PCNames; PC = state.HealthColors.PCNames; IsTypeOn = state.HealthColors.PCAura; PercentOn = state.HealthColors.auraPercPC; ShowDead = state.HealthColors.auraDeadPC; var cBy = oCharacter.get('controlledby'); var player = getObj('player', cBy); pColor = '#000000'; if(player !== undefined) pColor = player.get('color'); } //IF MONSTER------------ else if(type == 'Monster') { GM = state.HealthColors.GM_NPCNames; PC = state.HealthColors.NPCNames; IsTypeOn = state.HealthColors.NPCAura; PercentOn = state.HealthColors.auraPerc; ShowDead = state.HealthColors.auraDead; } else return; //CHECK DISABLED AURA/TINT ATTRIB------------ if(oCharacter !== undefined) { UseAura = lookupUseColor(oCharacter); } //SET HEALTH COLOR---------- if(IsTypeOn &amp;&amp; UseAura !== "NO") { percReal = Math.min(percReal, 100); if(percReal &gt; PercentOn || curValue === 0) SetAuraNone(obj); else TokenSet(obj, state.HealthColors.AuraSize, markerColor, pColor, update); //SHOW DEAD---------- if(ShowDead === true) { if(curValue &gt; 0) obj.set("status_dead", false); else if(curValue &lt; 1) { var DeadSounds = state.HealthColors.auraDeadFX; if(DeadSounds !== "None" &amp;&amp; curValue != prevValue) PlayDeath(DeadSounds); obj.set("status_dead", true); SetAuraNone(obj); } } } else if((!IsTypeOn || UseAura === "NO") &amp;&amp; obj.get(colortype + '_color') === markerColor) SetAuraNone(obj); //SET SHOW NAMES------------ SetShowNames(GM, PC, obj); //**FX for Damage/Healing using DeathTracker's approach------------ if(curValue != prevValue &amp;&amp; prevValue != "" &amp;&amp; update !== "YES") { let left = parseInt(obj.get('left')), top = parseInt(obj.get('top')); if(state.HealthColors.FX === true) { if(curValue &lt; prevValue) { // Damage FX – using HurtFX type (default: splatter-blood) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HurtFX, obj.get('_pageid')); } else if(curValue &gt; prevValue) { // Healing FX – using HealFX type (default: glow-holy) spawnFxBetweenPoints({ x: left, y: top }, { x: left, y: top }, state.HealthColors.HealFX, obj.get('_pageid')); } } } } }, /*------------------------ CHAT MESSAGES ------------------------*/ handleInput = function (msg) { var msgFormula = msg.content.split(/\s+/); var command = msgFormula[0].toUpperCase(), UPPER =""; if(msg.type == "api" &amp;&amp; command.indexOf("!AURA") !== -1) { var OPTION = msgFormula[1] || "MENU"; if(!playerIsGM(msg.playerid)) { sendChat('HealthColors', "/w " + msg.who + " you must be a GM to use this command!"); return; } else { if(OPTION !== "MENU") GMW("UPDATING TOKENS..."); switch(OPTION.toUpperCase()) { case "MENU": break; case "ON": state.HealthColors.auraColorOn = !state.HealthColors.auraColorOn; break; case "BAR": state.HealthColors.auraBar = "bar" + msgFormula[2]; break; case "TINT": state.HealthColors.auraTint = !state.HealthColors.auraTint; break; case "PERC": state.HealthColors.auraPercPC = parseInt(msgFormula[2], 10); state.HealthColors.auraPerc = parseInt(msgFormula[3], 10); break; case "PC": state.HealthColors.PCAura = !state.HealthColors.PCAura; break; case "NPC": state.HealthColors.NPCAura = !state.HealthColors.NPCAura; break; case "GMNPC": state.HealthColors.GM_NPCNames = msgFormula[2]; break; case "GMPC": state.HealthColors.GM_PCNames = msgFormula[2]; break; case "PCNPC": state.HealthColors.NPCNames = msgFormula[2]; break; case "PCPC": state.HealthColors.PCNames = msgFormula[2]; break; case "DEAD": state.HealthColors.auraDead = !state.HealthColors.auraDead; break; case "DEADPC": state.HealthColors.auraDeadPC = !state.HealthColors.auraDeadPC; break; case "DEADFX": state.HealthColors.auraDeadFX = msgFormula[2]; break; case "SIZE": state.HealthColors.AuraSize = parseFloat(msgFormula[2]); break; case "ONEOFF": state.HealthColors.OneOff = !state.HealthColors.OneOff; break; case "FX": state.HealthColors.FX = !state.HealthColors.FX; break; case "HEAL": UPPER = msgFormula[2]; state.HealthColors.HealFX = UPPER; break; case "HURT": UPPER = msgFormula[2]; state.HealthColors.HurtFX = UPPER; break; case "LISTFX": listCustomFX(); break; case "RESET": delete state.HealthColors; GMW("STATE RESET"); checkInstall(); break; case "UPDATE": manUpdate(msg); return; } aurahelp(OPTION); } } }, // New function: List Custom FX IDs in a formatted menu listCustomFX = function() { var allCustFX = findObjs({ _type: "custfx" }); var output = "&lt;div style='border: 1px solid #000; background-color: #f2f2f2; padding: 5px; border-radius: 4px; max-width: 400px;'&gt;"; output += "&lt;strong&gt;Custom FX IDs:&lt;/strong&gt;&lt;br&gt;&lt;ul style='list-style: none; padding: 0; margin: 0;'&gt;"; if(allCustFX.length &gt; 0) { allCustFX.forEach(function(custfx) { var fxName = custfx.get("name") || "[Unnamed]"; output += "&lt;li style='padding: 2px 0;'&gt;&lt;span style='font-weight:bold;'&gt;" + fxName + ":&lt;/span&gt; " + custfx.get("_id") + "&lt;/li&gt;"; }); } else { output += "&lt;li&gt;No custom FX objects found.&lt;/li&gt;"; } output += "&lt;/ul&gt;&lt;/div&gt;"; sendChat('HealthColors', "/w gm " + output); }, //SET TOKEN COLORS------------ TokenSet = function (obj, sizeSet, markerColor, pColor, update) { var Pageon = getObj("page", obj.get("_pageid")); var scale = Pageon.get("scale_number") / 10; if(state.HealthColors.auraTint === true) { if(obj.get('aura1_color') == markerColor &amp;&amp; update === "YES") { obj.set({'aura1_color': "transparent"}); } obj.set({'tint_color': markerColor}); } else { if(obj.get('tint_color') == markerColor &amp;&amp; update === "YES") { obj.set({'tint_color': "transparent"}); } obj.set({ 'aura1_radius': sizeSet * scale * 1.8, 'aura1_color': markerColor, 'showplayers_aura1': true }); } }, //REMOVE ALL------------ SetAuraNone = function (obj) { if(state.HealthColors.auraTint === true) obj.set({'tint_color': "transparent"}); else obj.set({'aura1_color': "transparent"}); }, //FORCE ALL TOKEN UPDATE------------ MenuForceUpdate = function(){ let i = 0; const start = new Date().getTime(); const barUsed = state.HealthColors.auraBar; const workQueue = findObjs({type: 'graphic', subtype: 'token', layer: 'objects'}) .filter((o) =&gt; o.get(barUsed + "_max") !== "" &amp;&amp; o.get(barUsed + "_value") !== ""); const drainQueue = () =&gt; { let t = workQueue.shift(); if(t){ const prev = JSON.parse(JSON.stringify(t)); handleToken(t, prev, 'YES'); setTimeout(drainQueue, 0); } else { sendChat('Fixing Tokens', `/w gm Finished Fixing Tokens`); } }; sendChat('Fixing Tokens', `/w gm Fixing ${workQueue.length} Tokens`); drainQueue(); var end = new Date().getTime(); return "Tokens Processed: " + workQueue.length + "&lt;br&gt;Run time in ms: " + (end - start); }, SetShowNames = function(GM, PC, obj) { if(GM != 'Off' &amp;&amp; GM != '') { GM = (GM == "Yes") ? true : false; obj.set({'showname': GM}); } if(PC != 'Off' &amp;&amp; PC != '') { PC = (PC == "Yes") ? true : false; obj.set({'showplayers_name': PC}); } }, //MANUAL UPDATE------------ manUpdate = function (msg) { var selected = msg.selected; var allNames = ''; _.each(selected, function (obj) { var token = getObj('graphic', obj._id); var tName = token.get("name"); allNames = allNames.concat(tName + '&lt;br&gt;'); var prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }); GMW(allNames); }, //ATTRIBUTE CACHE------------ makeSmartAttrCache = function (attribute, options) { let cache = {}, defaultValue = options.default || 'YES', validator = options.validation || _.constant(true); on('change:attribute', function (attr) { if(attr.get('name') === attribute) { if(!validator(attr.get('current'))) { attr.set('current', defaultValue); } cache[attr.get('characterid')] = attr.get('current'); var tokens = findObjs({type: 'graphic'}).filter((o) =&gt; o.get('represents') === attr.get("characterid")); _.each(tokens, function (obj) { var prev = JSON.parse(JSON.stringify(obj)); handleToken(obj, prev, "YES"); }); } }); on('destroy:attribute', function (attr) { if(attr.get('name') === attribute) { delete cache[attr.get('characterid')]; } }); return function(character){ let attr = findObjs({type: 'attribute', name: attribute, characterid: character.id}, {caseInsensitive:true})[0] || 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'); } return cache[character.id]; }; }, lookupUseBlood = makeSmartAttrCache('USEBLOOD', { default: 'DEFAULT' }), lookupUseColor = makeSmartAttrCache('USECOLOR', { default: 'YES', validation: (o) =&gt; o.match(/YES|NO/) }), //DEATH SOUND------------ PlayDeath = function (trackname) { var RandTrackName; if(trackname.indexOf(",") &gt; 0) { var tracklist = trackname.split(","); RandTrackName = tracklist[Math.floor(Math.random() * tracklist.length)]; } else RandTrackName = trackname; var track = findObjs({type: 'jukeboxtrack', title: RandTrackName})[0]; if(track) { track.set('playing', false); track.set('softstop', false); track.set('volume', 50); track.set('playing', true); } else { log(ScriptName + ": No track found named " + RandTrackName); } }, //PERC TO RGB------------ PercentToHEX = function (percent) { var HEX; if(percent &gt; 100) HEX = "#0000FF"; else { if(percent === 100) percent = 99; var r, g, b = 0; if(percent &lt; 50) { g = Math.floor(255 * (percent / 50)); r = 255; } else { g = 255; r = Math.floor(255 * ((50 - percent % 50) / 50)); } HEX = "#" + ((1 &lt;&lt; 24) + (r &lt;&lt; 16) + (g &lt;&lt; 8) + b).toString(16).slice(1); } return HEX; }, //HEX TO RGB------------ HEXtoRGB = function (hex) { let parts = (hex || '').match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); if(parts) { let rgb = _.chain(parts).rest().map((d) =&gt; parseInt(d, 16)).value(); rgb.push(1.0); return rgb; } return [0, 0, 0, 0.0]; }, //SPAWN FX------------ SpawnFX = function (Scale, HitSize, left, top, FX, pageid) { _.defaults(FX, { "maxParticles": 100, "duration": 100, "size": 100, "sizeRandom": 100, "lifeSpan": 100, "lifeSpanRandom": 100, "speed": 0, "speedRandom": 0, "angle": 0, "angleRandom": 0, "emissionRate": 100, "startColour": [255, 255, 255, 1], "endColour": [0, 0, 0, 1], "gravity": {"x": 0, "y": 0.0} }); var newFX = { "maxParticles": FX.maxParticles * HitSize, "duration": FX.duration * HitSize, "size": FX.size * Scale / 2, "sizeRandom": FX.sizeRandom * Scale / 2, "lifeSpan": FX.lifeSpan, "lifeSpanRandom": FX.lifeSpanRandom, "speed": FX.speed * Scale, "speedRandom": FX.speedRandom * Scale, "angle": FX.angle, "angleRandom": FX.angleRandom, "emissionRate": FX.emissionRate * HitSize * 2, "startColour": FX.startColour, "endColour": FX.endColour, "gravity": {"x": FX.gravity.x * Scale, "y": FX.gravity.y * Scale} }; spawnFxWithDefinition(left, top, newFX, pageid); }, //HELP MENU------------ aurahelp = function (OPTION) { var Update = ''; if(OPTION !== "MENU") Update = MenuForceUpdate(); var img = "background-image: -webkit-linear-gradient(left, #76ADD6 0%, #a7c7dc 100%);"; var tshadow = "-1px -1px #222, 1px -1px #222, -1px 1px #222, 1px 1px #222 , 2px 2px #222;"; // Base style for buttons with fixed width (for most items) var style = 'style="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;"'; // FX type buttons use a wider style var fxStyle = 'style="padding-top: 1px; text-align:center; font-size: 9pt; width: 80px; height: 14px; border: 1px solid black; margin: 1px; background-color: #6FAEC7; border-radius: 4px; box-shadow: 1px 1px 1px #707070;"'; // For buttons that should adjust to the text length, we use auto width. var autoStyle = 'style="padding: 1px 3px; text-align:center; font-size: 9pt; border: 1px solid black; margin: 1px; background-color: #6FAEC7; border-radius: 4px; box-shadow: 1px 1px 1px #707070; display:inline-block; width:auto;"'; var off = "#A84D4D"; var disable = "#D6D6D6"; var HR = "&lt;hr style='background-color: #000000; margin: 5px; border-width:0; color: #000000; height: 1px;'/&gt;"; var FX = state.HealthColors.auraDeadFX.substring(0, 4); // New button renamed "Custom FX ids" using autoStyle so it adjusts to the text length. var listFXButton = '&lt;a ' + autoStyle + ' href="!aura LISTFX"&gt;Custom FX ids&lt;/a&gt;&lt;br&gt;'; sendChat('HealthColors', "/w gm &lt;b&gt;&lt;br&gt;" + '&lt;div style="border-radius: 8px; padding: 5px; font-size: 9pt; text-shadow: ' + tshadow + '; box-shadow: 3px 3px 1px #707070; ' + img + ' color:#FFF; border:2px solid black; text-align:right; vertical-align:middle;"&gt;' + '&lt;u&gt;&lt;big&gt;HealthColors Version: ' + version + '&lt;/u&gt;&lt;/big&gt;&lt;br&gt;' + HR + 'Is On: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraColorOn !== true ? off : "") + ';" href="!aura on"&gt;' + (state.HealthColors.auraColorOn !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Bar: &lt;a ' + style + ' href="!aura bar ?{Bar|1|2|3}"&gt;' + state.HealthColors.auraBar + '&lt;/a&gt;&lt;br&gt;' + 'Use Tint: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraTint !== true ? off : "") + ';" href="!aura tint"&gt;' + (state.HealthColors.auraTint !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Percentage(PC/NPC): &lt;a ' + style + ' href="!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}"&gt;' + state.HealthColors.auraPercPC + '/' + state.HealthColors.auraPerc + '&lt;/a&gt;&lt;br&gt;' + HR + 'Show PC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.PCAura !== true ? off : "") + ';" href="!aura pc"&gt;' + (state.HealthColors.PCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show NPC Health: &lt;a ' + style + 'background-color:' + (state.HealthColors.NPCAura !== true ? off : "") + ';" href="!aura npc"&gt;' + (state.HealthColors.NPCAura !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead PC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDeadPC !== true ? off : "") + ';" href="!aura deadPC"&gt;' + (state.HealthColors.auraDeadPC !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'Show Dead NPC: &lt;a ' + style + 'background-color:' + (state.HealthColors.auraDead !== true ? off : "") + ';" href="!aura dead"&gt;' + (state.HealthColors.auraDead !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + HR + 'GM Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_PCNames, off, disable) + ';" href="!aura gmpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_PCNames + '&lt;/a&gt;&lt;br&gt;' + 'GM Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_NPCNames, off, disable) + ';" href="!aura gmnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.GM_NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'PC Sees all PC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.PCNames, off, disable) + ';" href="!aura pcpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.PCNames + '&lt;/a&gt;&lt;br&gt;' + 'PC Sees all NPC Names: &lt;a ' + style + 'background-color:' + ButtonColor(state.HealthColors.NPCNames, off, disable) + ';" href="!aura pcnpc ?{Setting|Yes|No|Off}"&gt;' + state.HealthColors.NPCNames + '&lt;/a&gt;&lt;br&gt;' + HR + 'Aura Size: &lt;a ' + style + ' href="!aura size ?{Size?|0.7}"&gt;' + state.HealthColors.AuraSize + '&lt;/a&gt;&lt;br&gt;' + 'One Offs: &lt;a ' + style + 'background-color:' + (state.HealthColors.OneOff !== true ? off : "") + ';" href="!aura ONEOFF"&gt;' + (state.HealthColors.OneOff !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'FX: &lt;a ' + style + 'background-color:' + (state.HealthColors.FX !== true ? off : "") + ';" href="!aura FX"&gt;' + (state.HealthColors.FX !== true ? "No" : "Yes") + '&lt;/a&gt;&lt;br&gt;' + 'HealFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HealFX + ';" href="!aura HEAL ?{FX Type?|glow-holy}"&gt;' + state.HealthColors.HealFX + '&lt;/a&gt;&lt;br&gt;' + 'HurtFX Type: &lt;a ' + fxStyle + 'background-color:#' + state.HealthColors.HurtFX + ';" href="!aura HURT ?{FX Type?|splatter-blood}"&gt;' + state.HealthColors.HurtFX + '&lt;/a&gt;&lt;br&gt;' + 'DeathSFX: &lt;a ' + style + ' href="!aura deadfx ?{Sound Name?|' + state.HealthColors.auraDeadFX + '}"&gt;' + FX + '&lt;/a&gt;&lt;br&gt;' + HR + listFXButton + Update + '&lt;/div&gt;'); }, //OFF BUTTON COLORS------------ ButtonColor = function (state, off, disable) { var color; if(state == "No") color = off; if(state == "Off") color = disable; return color; }, //CHECK INSTALL &amp; SET STATE------------ checkInstall = function () { log('-=&gt;' + ScriptName + ' v' + version + ' [Updated: ' + Updated + ']&lt;=-'); if(!_.has(state, 'HealthColors') || state.HealthColors.schemaVersion !== schemaVersion) { log('&lt;' + ScriptName + ' Updating Schema to v' + schemaVersion + '&gt;'); state.HealthColors = {schemaVersion: schemaVersion}; state.HealthColors.version = version; } //CHECK STATE VALUES if(_.isUndefined(state.HealthColors.auraColorOn)) state.HealthColors.auraColorOn = true; //global on or off if(_.isUndefined(state.HealthColors.auraBar)) state.HealthColors.auraBar = "bar1"; //bar to use if(_.isUndefined(state.HealthColors.auraTint)) state.HealthColors.auraTint = false; //use tint instead? if(_.isUndefined(state.HealthColors.auraPercPC)) state.HealthColors.auraPercPC = 100; //precent to start showing PC if(_.isUndefined(state.HealthColors.auraPerc)) state.HealthColors.auraPerc = 100; //precent to start showing NPC //----------------- if(_.isUndefined(state.HealthColors.PCAura)) state.HealthColors.PCAura = true; //show players Health? if(_.isUndefined(state.HealthColors.NPCAura)) state.HealthColors.NPCAura = true; //show NPC Health? if(_.isUndefined(state.HealthColors.auraDeadPC)) state.HealthColors.auraDeadPC = true; //show dead X status PC if(_.isUndefined(state.HealthColors.auraDead)) state.HealthColors.auraDead = true; //show dead X status NPC //----------------- if(_.isUndefined(state.HealthColors.GM_PCNames)) state.HealthColors.GM_PCNames = "Yes"; //show GM PC names? if(_.isUndefined(state.HealthColors.PCNames)) state.HealthColors.PCNames = "Yes"; //show players PC Names? //----------------- if(_.isUndefined(state.HealthColors.GM_NPCNames)) state.HealthColors.GM_NPCNames = "Yes"; //show GM NPC names? if(_.isUndefined(state.HealthColors.NPCNames)) state.HealthColors.NPCNames = "Yes"; //show players NPC Names? //----------------- if(_.isUndefined(state.HealthColors.AuraSize)) state.HealthColors.AuraSize = 0.7; //set aura size? if(_.isUndefined(state.HealthColors.FX)) state.HealthColors.FX = true; //set FX ON/OFF? // Updated defaults for FX types (using DeathTracker FX names) if(_.isUndefined(state.HealthColors.HealFX)) state.HealthColors.HealFX = "glow-holy"; //set Heal FX type if(_.isUndefined(state.HealthColors.HurtFX)) state.HealthColors.HurtFX = "splatter-blood"; //set Hurt FX type if(_.isUndefined(state.HealthColors.auraDeadFX)) state.HealthColors.auraDeadFX = 'None'; //Sound FX Name //TokenMod CHECK if('undefined' !== typeof TokenMod &amp;&amp; TokenMod.ObserveTokenChange) TokenMod.ObserveTokenChange(handleToken); var FXHurt = findObjs({_type: "custfx", name: "-DefaultHurt"}, {caseInsensitive: true})[0]; var FXHeal = findObjs({_type: "custfx", name: "-DefaultHeal"}, {caseInsensitive: true})[0]; //DEFAULT FX CHECK if(!FXHurt) { GMW("Creating Default Hurt FX"); var Hurt = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 3, "lifeSpan": 25, "lifeSpanRandom": 5, "speed": 8, "speedRandom": 3, "gravity": {"x": 0.01, "y": 0.65}, "angle": 270, "angleRandom": 25, "emissionRate": 100, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0] }; createObj('custfx', {name: "-DefaultHurt", definition: Hurt}); } if(!FXHeal) { GMW("Creating Default Heal FX"); var Heal = { "maxParticles": 150, "duration": 50, "size": 10, "sizeRandom": 15, "lifeSpan": 50, "lifeSpanRandom": 30, "speed": 0.5, "speedRandom": 2, "angle": 0, "angleRandom": 180, "emissionRate": 1000, "startColour": [0, 0, 0, 0], "endColour": [0, 0, 0, 0] }; createObj('custfx', {name: "-DefaultHeal", definition: Heal}); } }, //WHISPER GM------------ GMW = function (text) { var DIV = "&lt;div 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%);"; var MSG = DIV + "'&gt;&lt;b&gt;" + text + "&lt;/b&gt;&lt;/div"; sendChat('HealthColors', "/w GM " + MSG); }, //OUTSIDE CALL------------ UpdateToken = function (obj, prev) { if (obj.get("type") === "graphic") handleToken(obj, prev); else GMW("Script sent non-Token to be updated!"); }, //REGISTER TRIGGERS------------ registerEventHandlers = function () { on('chat:message', handleInput); on("change:token", handleToken); on('add:token', function (t) { _.delay(() =&gt; { let token = getObj('graphic', t.id), prev = JSON.parse(JSON.stringify(token)); handleToken(token, prev, "YES"); }, 400); }); //register this script to SmartAoE to handle linked bar hp changes if('undefined' !== typeof SmartAoE &amp;&amp; SmartAoE.ObserveTokenChange){ SmartAoE.ObserveTokenChange(function(obj, prev){ handleToken(obj, prev, "NO"); }); }; }; //RETURN OUTSIDE FUNCTIONS------------ return { GMW: GMW, Update: UpdateToken, CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); //On Ready on('ready', function () { 'use strict'; HealthColors.GMW("API READY"); HealthColors.CheckInstall(); HealthColors.RegisterEventHandlers(); }); Okay maybe this is a n00b question (sorry in advance) but is the idea to delete the Aura/Tint HealthColors script and then add a new script using the new code you've created? Thank you in advance!
1741479122
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Yep, that would be the procedure.