This was sent to me as part of a discussion about the same- certainly works for me - here's the original discussion <a href="https://app.roll20.net/forum/post/12478610/aura-tint-health-colors-and-jumpgate/?pageforid=12478610#post-12478610" rel="nofollow">https://app.roll20.net/forum/post/12478610/aura-tint-health-colors-and-jumpgate/?pageforid=12478610#post-12478610</a> /* 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") === "" && 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 && UseAura !== "NO") { percReal = Math.min(percReal, 100); if(percReal > PercentOn || curValue === 0) SetAuraNone(obj); else TokenSet(obj, state.HealthColors.AuraSize, markerColor, pColor, update); //SHOW DEAD---------- if(ShowDead === true) { if(curValue > 0) obj.set("status_dead", false); else if(curValue < 1) { var DeadSounds = state.HealthColors.auraDeadFX; if(DeadSounds !== "None" && curValue != prevValue) PlayDeath(DeadSounds); obj.set("status_dead", true); SetAuraNone(obj); } } } else if((!IsTypeOn || UseAura === "NO") && 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 && prevValue != "" && update !== "YES") { let left = parseInt(obj.get('left')), top = parseInt(obj.get('top')); if(state.HealthColors.FX === true) { if(curValue < 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 > 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" && 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 = "<div style='border: 1px solid #000; background-color: #f2f2f2; padding: 5px; border-radius: 4px; max-width: 400px;'>"; output += "<strong>Custom FX IDs:</strong><br><ul style='list-style: none; padding: 0; margin: 0;'>"; if(allCustFX.length > 0) { allCustFX.forEach(function(custfx) { var fxName = custfx.get("name") || "[Unnamed]"; output += "<li style='padding: 2px 0;'><span style='font-weight:bold;'>" + fxName + ":</span> " + custfx.get("_id") + "</li>"; }); } else { output += "<li>No custom FX objects found.</li>"; } output += "</ul></div>"; 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 && update === "YES") { obj.set({'aura1_color': "transparent"}); } obj.set({'tint_color': markerColor}); } else { if(obj.get('tint_color') == markerColor && 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) => o.get(barUsed + "_max") !== "" && o.get(barUsed + "_value") !== ""); const drainQueue = () => { 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 + "<br>Run time in ms: " + (end - start); }, SetShowNames = function(GM, PC, obj) { if(GM != 'Off' && GM != '') { GM = (GM == "Yes") ? true : false; obj.set({'showname': GM}); } if(PC != 'Off' && 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 + '<br>'); 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) => 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) => o.match(/YES|NO/) }), //DEATH SOUND------------ PlayDeath = function (trackname) { var RandTrackName; if(trackname.indexOf(",") > 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 > 100) HEX = "#0000FF"; else { if(percent === 100) percent = 99; var r, g, b = 0; if(percent < 50) { g = Math.floor(255 * (percent / 50)); r = 255; } else { g = 255; r = Math.floor(255 * ((50 - percent % 50) / 50)); } HEX = "#" + ((1 << 24) + (r << 16) + (g << 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) => 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 = "<hr style='background-color: #000000; margin: 5px; border-width:0; color: #000000; height: 1px;'/>"; 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 = '<a ' + autoStyle + ' href="!aura LISTFX">Custom FX ids</a><br>'; sendChat('HealthColors', "/w gm <b><br>" + '<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;">' + '<u><big>HealthColors Version: ' + version + '</u></big><br>' + HR + 'Is On: <a ' + style + 'background-color:' + (state.HealthColors.auraColorOn !== true ? off : "") + ';" href="!aura on">' + (state.HealthColors.auraColorOn !== true ? "No" : "Yes") + '</a><br>' + 'Bar: <a ' + style + ' href="!aura bar ?{Bar|1|2|3}">' + state.HealthColors.auraBar + '</a><br>' + 'Use Tint: <a ' + style + 'background-color:' + (state.HealthColors.auraTint !== true ? off : "") + ';" href="!aura tint">' + (state.HealthColors.auraTint !== true ? "No" : "Yes") + '</a><br>' + 'Percentage(PC/NPC): <a ' + style + ' href="!aura perc ?{PCPercent?|100} ?{NPCPercent?|100}">' + state.HealthColors.auraPercPC + '/' + state.HealthColors.auraPerc + '</a><br>' + HR + 'Show PC Health: <a ' + style + 'background-color:' + (state.HealthColors.PCAura !== true ? off : "") + ';" href="!aura pc">' + (state.HealthColors.PCAura !== true ? "No" : "Yes") + '</a><br>' + 'Show NPC Health: <a ' + style + 'background-color:' + (state.HealthColors.NPCAura !== true ? off : "") + ';" href="!aura npc">' + (state.HealthColors.NPCAura !== true ? "No" : "Yes") + '</a><br>' + 'Show Dead PC: <a ' + style + 'background-color:' + (state.HealthColors.auraDeadPC !== true ? off : "") + ';" href="!aura deadPC">' + (state.HealthColors.auraDeadPC !== true ? "No" : "Yes") + '</a><br>' + 'Show Dead NPC: <a ' + style + 'background-color:' + (state.HealthColors.auraDead !== true ? off : "") + ';" href="!aura dead">' + (state.HealthColors.auraDead !== true ? "No" : "Yes") + '</a><br>' + HR + 'GM Sees all PC Names: <a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_PCNames, off, disable) + ';" href="!aura gmpc ?{Setting|Yes|No|Off}">' + state.HealthColors.GM_PCNames + '</a><br>' + 'GM Sees all NPC Names: <a ' + style + 'background-color:' + ButtonColor(state.HealthColors.GM_NPCNames, off, disable) + ';" href="!aura gmnpc ?{Setting|Yes|No|Off}">' + state.HealthColors.GM_NPCNames + '</a><br>' + HR + 'PC Sees all PC Names: <a ' + style + 'background-color:' + ButtonColor(state.HealthColors.PCNames, off, disable) + ';" href="!aura pcpc ?{Setting|Yes|No|Off}">' + state.HealthColors.PCNames + '</a><br>' + 'PC Sees all NPC Names: <a ' + style + 'background-color:' + ButtonColor(state.HealthColors.NPCNames, off, disable) + ';" href="!aura pcnpc ?{Setting|Yes|No|Off}">' + state.HealthColors.NPCNames + '</a><br>' + HR + 'Aura Size: <a ' + style + ' href="!aura size ?{Size?|0.7}">' + state.HealthColors.AuraSize + '</a><br>' + 'One Offs: <a ' + style + 'background-color:' + (state.HealthColors.OneOff !== true ? off : "") + ';" href="!aura ONEOFF">' + (state.HealthColors.OneOff !== true ? "No" : "Yes") + '</a><br>' + 'FX: <a ' + style + 'background-color:' + (state.HealthColors.FX !== true ? off : "") + ';" href="!aura FX">' + (state.HealthColors.FX !== true ? "No" : "Yes") + '</a><br>' + 'HealFX Type: <a ' + fxStyle + 'background-color:#' + state.HealthColors.HealFX + ';" href="!aura HEAL ?{FX Type?|glow-holy}">' + state.HealthColors.HealFX + '</a><br>' + 'HurtFX Type: <a ' + fxStyle + 'background-color:#' + state.HealthColors.HurtFX + ';" href="!aura HURT ?{FX Type?|splatter-blood}">' + state.HealthColors.HurtFX + '</a><br>' + 'DeathSFX: <a ' + style + ' href="!aura deadfx ?{Sound Name?|' + state.HealthColors.auraDeadFX + '}">' + FX + '</a><br>' + HR + listFXButton + Update + '</div>'); }, //OFF BUTTON COLORS------------ ButtonColor = function (state, off, disable) { var color; if(state == "No") color = off; if(state == "Off") color = disable; return color; }, //CHECK INSTALL & SET STATE------------ checkInstall = function () { log('-=>' + ScriptName + ' v' + version + ' [Updated: ' + Updated + ']<=-'); if(!_.has(state, 'HealthColors') || state.HealthColors.schemaVersion !== schemaVersion) { log('<' + ScriptName + ' Updating Schema to v' + schemaVersion + '>'); 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 && 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 = "<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 + "'><b>" + text + "</b></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(() => { 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 && 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(); });