Evening, I am using a few scripts, that all work together fairly well. But I would like to add AlterBars script to the mix The scripts I am using that work well are Aura/Tint Health Colors and a Hp Bar Management script (by The Aaron). Those work perfectly together. The problem I am running in to is having the Hp Bar Management script register changes made through the AlterBars script. I am running the Health Colors script and trying to run the AlterBars script, both modified to work with each other. Not being a coder, I don't know what to add to what script to have the Hp Bar Management script watch for changes made by the AlterBars script. Below are the two scripts in question = Hp Bar Management code first (modified for Pathfinder 1e with Mythic Rules)...and the AlterBars script second. Sorry for the long post (due to the scripts, and no "spoiler" to hide them) /* global TokenMod, ChatSetAttr */ on('ready', () => { // Configuration parameters const HPBarNum = 1; const TempHPMarker = 'green'; const DeadMarker = 'dead'; const BloodiedMarker = 'red'; const TempHPAttributeName = 'hp_temp'; const UseBloodied=true; //New Constants const DyingAt = 0; const DisableMarker = 'half-heart'; const DyingMarker = 'skull'; const NegativeDeadAtAttr = 'constitution'; // Cases for Mythic Tier and Greater Tenacity const MythicYN = 'Mythic-Tier'; const MasterLvl = 'Master-Level'; ///////////////////////////////////////////// const clearURL = /images\/4277467\/iQYjFOsYC5JsuOPUCI9RGA\/.*.png/; const bar = `bar${HPBarNum}_value`; const lnk = `bar${HPBarNum}_link`; const max = `bar${HPBarNum}_max`; const unpackSM = (stats) => stats.split(/,/).reduce((m,v) => { let p = v.split(/@/); let n = parseInt(p[1] || '0', 10); if(p[0].length) { m[p[0]] = Math.max(n, m[p[0]] || 0); } return m; },{}); const packSM = (o) => Object.keys(o) .map(k => ('dead' === k || true === o[k] || o[k]<1 || o[k]>9) ? k : `${k}@${parseInt(o[k])}` ).join(','); const checkTempHP = (obj) => { let v = parseFloat(obj.get('current')); findObjs({ type: 'graphic', represents: obj.get('characterid') }) .filter( (t) => t.get(lnk) !== '') .filter( (t) => !clearURL.test(t.get('imgsrc') ) ) .forEach((g)=>{ let sm = unpackSM(g.get('statusmarkers')); if(v>0){ sm[TempHPMarker]=v; } else { delete sm[TempHPMarker]; } g.set({ statusmarkers: packSM(sm) }); }); }; const assureTempHPMarkers = () => { let queue = findObjs({ type: 'attribute', name: TempHPAttributeName }); const burndownQueue = ()=>{ if(queue.length){ let attr = queue.shift(); checkTempHP(attr); setTimeout(burndownQueue,0); } }; burndownQueue(); }; const temporalTempHPCache = {}; const accountForHPBarChange = (obj,prev) => { // 1. did hp change and is it a scale const hpMax = parseInt(obj.get(max),10); let hp = parseInt(obj.get(bar),10); const diff = hp-parseFloat(prev[bar]); if( !isNaN(hpMax) && diff !== 0 ) { let changes = {}; // 2. does it represent a character // 3. does the hp bar represent an attribute const character = getObj('character',obj.get('represents')); if( diff < 0 && character && obj.get(lnk)!=='' ){ // 4. is there temp hp const temp_hp = findObjs({ type: 'attribute', characterid: character.id, name: TempHPAttributeName })[0]; if( temp_hp ) { const now = Date.now(); const tempHP = parseFloat(temp_hp.get('current'))||0; const newTmpHP = Math.max((tempHP+diff),0); const toHeal = tempHP - newTmpHP; const hash=`${character.id}:${hp}:${hpMax}:${toHeal}`; // 5. have we accounted for it. if( !temporalTempHPCache.hasOwnProperty(hash) || (now-temporalTempHPCache[hash].when)>10000) { // calculate necessary change temporalTempHPCache[hash]={ when: now, toHeal: toHeal }; temp_hp.set('current', newTmpHP); checkTempHP(temp_hp); } hp += temporalTempHPCache[hash].toHeal; changes[bar] = hp; } } // NEW const dying_at = DyingAt; let dead_at=0; const mythic_rank = parseFloat((findObjs({ type: 'attribute', characterid: character.id, name: MythicYN })[0]||{get:()=>0}).get('current')); const lvl_mstr = parseFloat((findObjs({ type: 'attribute', characterid: character.id, name: MasterLvl })[0]||{get:()=>0}).get('current')); if(NegativeDeadAtAttr.length && character){ // Add case for Mythic dead at double constitution and Master level if( mythic_rank > 0 ){ if( lvl_mstr > 0 ){ dead_at = -((2 * parseFloat((findObjs({ type: 'attribute', characterid:character.id, name: NegativeDeadAtAttr })[0]||{get:()=>0}).get('current'))) + (parseFloat((findObjs({ type: 'attribute', characterid:character.id, name: MasterLvl })[0]||{get:()=>0}).get('current')))); } else { dead_at = -(2 * parseFloat((findObjs({ type: 'attribute', characterid: character.id, name: NegativeDeadAtAttr })[0]||{get:()=>0}).get('current'))); } } else { dead_at = -parseFloat((findObjs({ type: 'attribute', characterid:character.id, name: NegativeDeadAtAttr })[0]||{get:()=>0}).get('current')); } } // END NEW let sm = unpackSM(obj.get('statusmarkers')); if(hp > hpMax) { hp = hpMax; changes[bar] = hp; delete sm[DisableMarker]; delete sm[DyingMarker]; delete sm[DeadMarker]; if(UseBloodied) { delete sm[BloodiedMarker]; } // NEW conditions } else if(hp===dying_at) { changes[bar] = hp; sm[DisableMarker] = true; delete sm[DyingMarker]; delete sm[DeadMarker]; if(UseBloodied) { delete sm[BloodiedMarker]; } } else if( (hp < dying_at) && (hp > dead_at) ) { changes[bar] = hp; delete sm[DisableMarker]; sm[DyingMarker] = true; delete sm[DeadMarker]; if(UseBloodied) { delete sm[BloodiedMarker]; } } else if(hp <= dead_at) { // End new conditions, Below line gets uncommented out if removing dying_at and dead_at. Remove dead_at line below also. // } else if(hp <= 0) { // hp=0; hp=dead_at; changes[bar] = hp; delete sm[DisableMarker]; delete sm[DyingMarker]; sm[DeadMarker] = true; if(UseBloodied) { delete sm[BloodiedMarker]; } } else if(UseBloodied && (hp <= (hpMax/2) ) ) { delete sm[DisableMarker]; delete sm[DyingMarker]; delete sm[DeadMarker]; sm[BloodiedMarker] = true; } else { delete sm[DisableMarker]; delete sm[DyingMarker]; delete sm[DeadMarker]; if(UseBloodied) { delete sm[BloodiedMarker]; } } changes.statusmarkers = packSM(sm); obj.set(changes); } }; const onAttributeChange = (obj) => { if(obj.get('name') === TempHPAttributeName){ checkTempHP(obj); } }; on("change:attribute", onAttributeChange); on("change:token", accountForHPBarChange); if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){ TokenMod.ObserveTokenChange(accountForHPBarChange); } if('undefined' !== typeof ChatSetAttr && ChatSetAttr.registerObserver){ ChatSetAttr.registerObserver('change',onAttributeChange); } if('undefined' !== typeof ApplyDamage && ApplyDamage.registerObserver){ ApplyDamage.registerObserver('change',onAttributeChange); } assureTempHPMarkers(); }); // VERSION INFO var AlterBars_Author = "SkyCaptainXIII"; var AlterBars_Version = "2.0.5"; var AlterBars_LastUpdated = 1494507176; // FUNCTION DECLARATION var AlterScript = AlterScript || {}; on("chat:message", function (msg) { if (msg.type != "api") return; if (msg.content.split(" ", 1)[0] === "!alter") { msg.who = msg.who.replace(" (GM)", ""); msg.content = msg.content.replace(/<br\/>\n/g, ' ').replace(/({{(.*?)}})/g, " $2 "); if (msg.content.indexOf("--target") == -1) { sendChat("ERROR", "/w " + msg.who + " Your macro does not have a target specified."); return; } AlterScript.Process(msg); } }); on("ready", function () { log("-=> AlterBars v" + AlterBars_Version + " <=- [" + (new Date(AlterBars_LastUpdated * 1000)) + "]"); //log (Date.now().toString().substr(0, 10)); }); // FUNCTIONS AlterScript.Process = function (msg) { // BAR CONFIGURATION - These are used to identify which bar to adjust. You can // also use any lowercase letters as well such as 'h' or 'hp' for hit points/health. var Bar1Key = "1"; var Bar2Key = "2"; var Bar3Key = "3"; var BarGain = ["gains", "point", "points"]; var BarLoss = ["loses", "point", "points"]; // OUTPUT FORMAT - The following variables are used to define how AlterBar // sends the output to chat. The first item is the verb, the second is the // singular name of the 'bar' and the third item is the plural version. // Example: Paladin heals 1 hit point. // Example: Paladin heals 8 hit points. // Example: Mage spends 3 mana. var Bar1Gain = ["heals", "hit point", "hit points"]; var Bar1Loss = ["takes", "damage", "damage"]; var Bar2Gain = ["recovers", "mana", "mana"] var Bar2Loss = ["spends", "mana", "mana"] var Bar3Gain = ["heals", "hit point", "hit points"]; var Bar3Loss = ["takes", "damage", "damage"]; // ALTCOLORS - This uses a dark green or dark red emote for gain / loss instead // of the default orange for both. var ALT_COLORS = true; // PREVENT OVERMAX - Set this variable to true to prevent the current value of // the bar from being greater than its max value. If there is no max value set, // it will not stop the current bar value from increasing. var PREVENT_OVERMAX = false; // STOP AT ZERO - Prevents the current value of the bar from dropping below zero. var STOP_AT_ZERO = false; var STOP_AT_DEAD = false; const DeadValue = 'CON'; // ANNOUNCE CHANGE IN CHAT - Set to true to send a message to the chat window // showing which token gained or lost points and how much. var ANNOUNCE_CHANGE = true; // SEND TO GM - Set to true to send the results to the GM. This will also trigger // if a hidden change is sent. var ALERT_GM = false; // Variables stuff... var n = msg.content.split(/\s+--/); var who = msg.who; var Target = ""; var Bar = 0; var AlterValue = 0; // Pull variables from n... n.shift(); _.each(n, function (a) { Tag = a.substring(0, a.indexOf("|")).trim(); Content = a.substring(a.indexOf("|") + 1).trim(); if (Tag.toLowerCase() == "target") Target = getObj("graphic", Content); if (Tag.toLowerCase() == "bar") Bar = Content; if (Tag.toLowerCase() == "amount") AlterValue = Content; if (Tag.toLowerCase() == "show" && Content.toLowerCase() == "gm") { ANNOUNCE_CHANGE = false; ALERT_GM = true; } if (Tag.toLowerCase() == "show" && Content.toLowerCase() == "none") { ANNOUNCE_CHANGE = false; ALERT_GM = false; } }); if(!Target) { sendChat("ERROR", `/w "${who}" Not a valid target id.`); return; } if (Bar == 1 || Bar == Bar1Key) { BarGain = Bar1Gain; BarLoss = Bar1Loss; } else if (Bar == 2 || Bar == Bar2Key) { BarGain = Bar2Gain; BarLoss = Bar2Loss; } else if (Bar == 3 || Bar == Bar3Key) { BarGain = Bar3Gain; BarLoss = Bar3Loss; } else { sendChat("ERROR", "/w " + who + " That is not a valid bar."); return; } // Get current and max values from the token... var CurrentValue = parseInt(Target.get("bar" + Bar + "_value")); var MaxValue = parseInt(Target.get("bar" + Bar + "_max")); // Set current values as previous values for transit to Tint/Aura HealthColors... var Previous = JSON.parse(JSON.stringify(Target)); // Check for a + or - sign... var Operand1 = AlterValue.charAt(0); var Operand2 = AlterValue.charAt(1); if (Operand2 === "+" || Operand2 === "-") AlterValue = AlterValue.substring(2); else if (Operand1 === "+" || Operand1 === "-") AlterValue = AlterValue.substring(1); // Save the value for the tooltip... var Expression = AlterValue; // Define CSS... var AlertGainStyle = "max-height: 20px; width: 100%; padding: 3px 40px 6px 0px; border: 1px solid #000; border-radius: 4px; background-color: " + + "; background-image: linear-gradient(rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); font-family: Candal; font-style: normal; font-size: 1.05em; line-height: 1em; color: #FFF; font-weight: normal; text-align: left; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;"; var AlertLossStyle = "max-height: 20px; width: 100%; padding: 3px 0px 6px 0px; border: 1px solid #000; border-radius: 4px; background-color: " + ((!ALT_COLORS) ? "#BF5700" : "#440000") + "; background-image: linear-gradient(rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); font-family: Candal; font-style: normal; font-size: 1.05em; line-height: 1em; color: #FFF; font-weight: normal; text-align: left; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;"; var AlertOuterStyle = "max-height: 40px; width: 100%; margin: 10px 0px 5px -7px; line-height: 40px;"; var AlertImageStyle = "height: 40px; width: 40px; float: right; margin: -11px 5px 0px 0px;"; // Main process... sendChat("", "/r " + AlterValue, function (outs) { AlterValue = parseInt(JSON.parse(outs[0].content).total); var Tooltip = "Rolling " + Expression + " = " + AlterValue + "' class='a inlinerollresult showtip tipsy-n'"; var TargetName = (Target.get("name") == "" || Target.get("showplayers_name") == false && !ALERT_GM) ? "NPC" : Target.get("name"); if (Operand1 != "-") { // Add to bar... if (PREVENT_OVERMAX) AlterValue = (AlterValue + CurrentValue > MaxValue) ? MaxValue - CurrentValue : AlterValue; var AlertGain = "" + "<div style='display: block; margin-left: -7px; margin-right: 2px; padding: 2px 0px;'>" + "<div style='position: relative; border: 1px solid #000; border-radius: 5px; background-color: " + ((!ALT_COLORS) ? "#BF5700" : "#004400") + "; background-image: linear-gradient(rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); margin-right: -2px; padding: 2px 5px 5px 50px;'>" + "<div style='position: absolute; top: -10px; left: 5px; height: 40px; width: 40px;'>" + "<img src='" + Target.get("imgsrc") + "' style='height: 40px; width: 40px;'></img>" + "</div>" + "<div style='font-family: Candal; font-size: 13px; line-height: 15px; color: #FFF; font-weight: normal; text-align: center; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;'>" + TargetName + " " + BarGain[0] + " " + AlterValue + " " + ((AlterValue == 1) ? BarGain[1] : BarGain[2]) + "." + "</div>" + "</div>" + "</div>"; if (ANNOUNCE_CHANGE) sendChat("", "/desc " + AlertGain); if (ALERT_GM) sendChat(who, "/w GM " + AlertGain); Target.set("bar" + Bar + "_value", CurrentValue += AlterValue); } else { // Subtract from bar... var AlertLoss = "" + "<div style='display: block; margin-left: -7px; margin-right: 2px; padding: 2px 0px;'>" + "<div style='position: relative; border: 1px solid #000; border-radius: 5px; background-color: " + ((!ALT_COLORS) ? "#BF5700" : "#440000") + "; background-image: linear-gradient(rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); margin-right: -2px; padding: 2px 5px 5px 50px;'>" + "<div style='position: absolute; top: -10px; left: 5px; height: 40px; width: 40px;'>" + "<img src='" + Target.get("imgsrc") + "' style='height: 40px; width: 40px;'></img>" + "</div>" + "<div style='font-family: Candal; font-size: 13px; line-height: 15px; color: #FFF; font-weight: normal; text-align: center; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;'>" + TargetName + " " + BarLoss[0] + " " + AlterValue + " " + ((AlterValue == 1) ? BarLoss[1] : BarLoss[2]) + "." + "</div>" + "</div>" + "</div>"; if (ANNOUNCE_CHANGE) sendChat("", "/desc " + AlertLoss); if (ALERT_GM) sendChat(who, "/w GM " + AlertLoss); // if (STOP_AT_ZERO && (CurrentValue - AlterValue < 0)) AlterValue = CurrentValue; if (STOP_AT_DEAD && (CurrentValue - AlterValue < DeadValue)) AlterValue = DeadValue Target.set("bar" + Bar + "_value", CurrentValue -= AlterValue); } if ('undefined' !== typeof HealthColors && HealthColors.Update) HealthColors.Update(Target, Previous); }); };