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

Aura/Tint HealthColors problems

I just installed this script. My players love it. But, I've noticed some problems: 1. Marking dead NPCs doesn't seem to work if the NPC is a NPC that does not come from the compendium. That is, if the NPC "card" is from a installed module, add-on, or homebrew, the script doesn't mark it as dead. 2. The sound doesn't always play. In fact, this could be related to whatever is causing the problem above. 3. Sometimes, reducing a NPC to 0 hp causes the NPC to have no tint color at all and it is not marked as dead. I've compared the token information between a creature from a module and a creature from the compendium and I don't see a difference. As an example (and what recently happened): a "froghemoth" pulled from the Tomb of Annihilation module's list of creatures does not work but a "froghemoth" pulled from the compendium does. There is no difference that I can see between the two tokens and their settings. Anyone with a better clue than I, know what's going on and how to fix this? Thanks.
1653195777

Edited 1653195825
Victor B.
Pro
Sheet Author
API Scripter
No, something is hosed.  You want apply damage in addition to aura/tint.  It works fine.  The first bar has to be HP and the second bar has to be AC, if you've changed that you shouldn't use this scirpt.  
1653195947

Edited 1653196201
Victor B.
Pro
Sheet Author
API Scripter
And this is the apply damage script, create a new script and copy the below,  which isn't in Roll20 but makes Aura/Tine shine.  Note that it doesn't do advantaged or disadvantaged rolls.  You'll need to roll that separate
1653195956
Victor B.
Pro
Sheet Author
API Scripter
/* global log, _, getObj, HealthColors, playerIsGM, sendChat, on */ const ApplyDamage = (() => {   "use strict";   const version = "1.1",     observers = {       "change": [],     },     boundedBar = false,     checkInstall = () => {       log(`-=> ApplyDamage v${version} <=-`);     },     defaultOpts = {       type: "half",       ids: "",       saves: "",       DC: "-1",       dmg: "0",       bar: "1"     },     statusMarkers = [       "red", "blue", "green", "brown", "purple", "pink", "yellow", "dead", "skull", "sleepy", "half-heart",       "half-haze", "interdiction", "snail", "lightning-helix", "spanner", "chained-heart", "chemical-bolt",       "death-zone", "drink-me", "edge-crack", "ninja-mask", "stopwatch", "fishing-net", "overdrive", "strong",       "fist", "padlock", "three-leaves", "fluffy-wing", "pummeled", "tread", "arrowed", "aura", "back-pain",       "black-flag", "bleeding-eye", "bolt-shield", "broken-heart", "cobweb", "broken-shield", "flying-flag",       "radioactive", "trophy", "broken-skull", "frozen-orb", "rolling-bomb", "white-tower", "grab", "screaming",       "grenade", "sentry-gun", "all-for-one", "angel-outfit", "archery-target"     ],     getWhisperPrefix = (playerid) => {       const player = getObj("player", playerid);       if (player && player.get("_displayname")) {         return `/w "${player.get("_displayname")}" `;       }       else {         return "/w GM ";       }     },     parseOpts = (content, hasValue) => {       return content         .replace(/<br\/>\n/g, " ")         .replace(/({{(.*?)\s*}}\s*$)/g, "$2")         .split(/\s+--/)         .slice(1)         .reduce((opts, arg) => {           const kv = arg.split(/\s(.+)/);           if (hasValue.includes(kv[0])) {             opts[kv[0]] = (kv[1] || "");           } else {             opts[arg] = true;           }           return opts;         }, {});     },     processInlinerolls = function (msg) {       if (msg.inlinerolls && msg.inlinerolls.length) {         return msg.inlinerolls.map(v => {           const ti = v.results.rolls.filter(v2 => v2.table)             .map(v2 => v2.results.map(v3 => v3.tableItem.name).join(", "))             .join(", ");           return (ti.length && ti) || v.results.total || 0;         }).reduce((m, v, k) => m.replace(`$[[${k}]]`, v), msg.content);       } else {         return msg.content;       }     },     handleError = (whisper, errorMsg) => {       const output = `${whisper}<div style="border:1px solid black;background:#FFBABA;padding:3px">` +         `<h4>Error</h4><p>${errorMsg}</p></div>`;       sendChat("ApplyDamage", output);     },     finalApply = (results, dmg, type, bar, status) => {       const barCur = `bar${bar}_value`,         barMax = `bar${bar}_max`;       Object.entries(results).forEach(([id, saved]) => {         const token = getObj("graphic", id),           prev = JSON.parse(JSON.stringify(token || {}));         let newValue;         if (token && !saved) {           if (boundedBar) {             newValue = Math.min(Math.max(parseInt(token.get(barCur)) - dmg, 0), parseInt(token.get(barMax)));           } else {             newValue = parseInt(token.get(barCur)) - dmg;           }           if (status) token.set(`status_${status}`, true);         }         else if (token && type === "half") {           if (boundedBar) {             newValue = Math.min(Math.max(parseInt(token.get(barCur)) - Math.floor(dmg / 2), 0), parseInt(token.get(barMax)));           } else {             newValue = parseInt(token.get(barCur)) - Math.floor(dmg / 2);           }         }         if (!_.isUndefined(newValue)) {           if (Number.isNaN(newValue)) newValue = token.get(barCur);           token.set(barCur, newValue);           notifyObservers("change", token, prev);         }       });     },     handleInput = (msg) => {       if (msg.type === "api" && msg.content.search(/^!apply-damage\b/) !== -1) {         const hasValue = ["ids", "saves", "DC", "type", "dmg", "bar", "status"],           opts = Object.assign({}, defaultOpts, parseOpts(processInlinerolls(msg), hasValue));         opts.ids = opts.ids.split(/,\s*/g);         opts.saves = opts.saves.split(/,\s*/g);         opts.DC = parseInt(opts.DC);         opts.dmg = parseInt(opts.dmg);         if (!playerIsGM(msg.playerid) && getObj("player", msg.playerid)) {           handleError(getWhisperPrefix(msg.playerid), "Permission denied.");           return;         }         if (!["1", "2", "3"].includes(opts.bar)) {           handleError(getWhisperPrefix(msg.playerid), "Invalid bar.");           return;         }         if (opts.status === "none") {           delete opts.status;         }         if (opts.status && !statusMarkers.includes(opts.status)) {           handleError(getWhisperPrefix(msg.playerid), "Invalid status.");           return;         }         const results = _.reduce(opts.ids, function (m, id, k) {           m[id] = parseInt(opts.saves[k] || "0") >= opts.DC;           return m;         }, {});         finalApply(results, opts.dmg, opts.type, opts.bar, opts.status);         const output = `${           getWhisperPrefix(msg.playerid)         }<div style="border:1px solid black;background:#FFF;padding:3px"><p>${           (opts.dmg ? `${opts.dmg} damage applied to tokens, with ${             (opts.type === "half" ? "half" : "no")           } damage on a successful saving throw.` : "")}${           (opts.status ? ` ${opts.status} status marker applied to tokens that failed the save.` : "")         }</p></div>`;         sendChat("ApplyDamage", output, null, { noarchive: true });       }       return;     },     notifyObservers = (event, obj, prev) => {       observers[event].forEach(observer => observer(obj, prev));     },     registerObserver = (event, observer) => {       if (observer && _.isFunction(observer) && observers.hasOwnProperty(event)) {         observers[event].push(observer);       } else {         log("ApplyDamage event registration unsuccessful.");       }     },     registerEventHandlers = () => {       on("chat:message", handleInput);     };   return {     checkInstall,     registerEventHandlers,     registerObserver   }; })(); on("ready", () => {   "use strict";   ApplyDamage.checkInstall();   ApplyDamage.registerEventHandlers();   if ("undefined" !== typeof HealthColors) {     ApplyDamage.registerObserver("change", HealthColors.Update);   } });
1653196029
Victor B.
Pro
Sheet Author
API Scripter
And this is the macro you want to run to kick it off
1653196077
Victor B.
Pro
Sheet Author
API Scripter
!group-check {{ --?{Ability Save|Strength,Strength Save|Dexterity,Dexterity Save|Constitution,Constitution Save|Intelligence,Intelligence Save|Wisdom,Wisdom Save|Charisma,Charisma Save} --process --subheader vs DC ?{DC} --button ApplyDamage !apply-damage ~dmg [[?{Damage}]] ~type ?{Damage on Save|Half,half|None,none} ~DC ?{DC} ~saves RESULTS(,) ~ids IDS(,) }}
1653196162
Victor B.
Pro
Sheet Author
API Scripter
So click on group check macro, enter damage/DC/and 1/2 save if failed.  Apply damage shows the affect models, which you need to select for group check and click apply damage and watch your horrendously slow combats suddenly speed up
I am not doing all that stuff. I just have a monster token on the battlemap. I click on the token. I subtract the hp/damage from bar 1. Aura/tint does the right thing if the monster token is from the compendium. It does not do the right thing if the token got dropped from a module, or is a homebrew.
1653265783

Edited 1653265891
Oosh
Sheet Author
API Scripter
A quick look at the script, monster (npc) detection is done on the basis of the 'controlledby' field. Do the buggy sources perhaps have something in either the character sheet's or token's 'Controllled By' fields? I'm not sure why this would matter, it should just be treated as a player (and presumably still work) even if the monster detection is off. I can't see anything else (apart from the bars being incorrect on the token, but you've obviously checked those) that would cause it to fail. The sandbox isn't failing at all, is it? So the script will fail to do anything for a homebrew token, then without restarting the sandbox, will work fine for a base compendium npc?
Oosh, you found it! It looks like the monsters being dropped from the module's list are controlled by me, whereas the ones dropped from the compendium are controlled by no one. With some experimentation, it looks like that's where the problem is. Taking one of the monsters where the script was failing, and editing the "controlled by" field to be no one, makes the script work. Changing it back to being owned by me, makes it fail again. Changing it back to being controlled by no one, the script works again... so clearly having the "controlled by" field being set to anything other than blank makes it fail... And that was definitely not obvious...  Thank you!
1653288393
Oosh
Sheet Author
API Scripter
No problem at all. Is it the character sheet where the 'controlledby' setting is coming from, or just in the default token settings? Do you want a short script to clear the 'controlledby' field when a new sheet is added to the game? I'm not sure I see the point in automatically adding the GM as an owner, I can't think what it would achieve, except for the edge case of preventing a co-GM from getting messages whispered to that character (and private rolls I guess?).
@Oosh: It's in the character sheet, not the token as far as I can tell. I'm just going to quickly go through all the sheets and make sure the "controlled by" field is blank. Hopefully, that will fix it.
1653362555
Victor B.
Pro
Sheet Author
API Scripter
@Oosh, shouldn't the token be controlled by the GM if dropped from the compendium?  I haven't had any issues like this.      
1653362639
Victor B.
Pro
Sheet Author
API Scripter
@Saul J.  Your tokens should be assigned to character sheets.  The character sheets are controlled by GM or players.  Are you dropping tokens not assigned to character sheets? 
1653364084
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Victor B. said: @Oosh, shouldn't the token be controlled by the GM if dropped from the compendium?  I haven't had any issues like this.       Only in the sense that GMs control all tokens. That's different from being a listed as a controller.
@Victor B: The only time I'm dropping tokens that aren't assigned to character sheets is when I'm creating new tokens/characters and in the process of assigning the tokens to a sheet. My problem is with creatures dropped from the compendium vs creatures dropped from a published module - in my current case it's Tomb of Annihilation. For some reason, the creatures dropped from ToA's folders of creatures and NPCs, at least sometimes, when I look at their sheets (not the tokens) list me specifically in the "controlled by" box.
1653612827
Oosh
Sheet Author
API Scripter
That sounds like a bug to me, but Keith is much more knowledgable on Content related stuff.
1653627887
Victor B.
Pro
Sheet Author
API Scripter
I drag compendium creatures and creatures from modules.  Aura/tint doesn't blink.  I'm not sure why, but something is off.  It should just work.  
I don't know what that something is. My last game was rather frustrating for me (not the players) because the aura/tint wasn't working at all. That is to say, the monsters were normal looking (no tint), and didn't change tint, or get the dead marker or anything throughout the night. And it was several monsters, some from the compendium and some from the module. The player's tokens were just green... and didn't change tint at all either. Yet, other scripts were working so I know it wasn't the API engine that crashed. I can't explain it - I just have nights like that where one script won't work correctly when everything else does. I have noticed one thing though, and I'm not sure if it's a bug or by design but if I put the monster down on the GM layer, it doesn't get the tint, and when I move it to the token layer, it doesn't get the tint. The tint only seems to get applied if the token is put directly on the token layer.
Saul J. said: I don't know what that something is. My last game was rather frustrating for me (not the players) because the aura/tint wasn't working at all. That is to say, the monsters were normal looking (no tint), and didn't change tint, or get the dead marker or anything throughout the night. And it was several monsters, some from the compendium and some from the module. The player's tokens were just green... and didn't change tint at all either. Yet, other scripts were working so I know it wasn't the API engine that crashed. I can't explain it - I just have nights like that where one script won't work correctly when everything else does. Just curious: did you try restarting the API sandbox anyways? I wonder if maybe it spun up but didn't include the Aura/Tint Healthcolors for some reason.  
I restarted the sandbox twice.
Saul J. said: I restarted the sandbox twice. Well that's odd.  Do you get a config menu output when you use the !aura command? It should look something like this: Are you using the script from the one-click, or a modified/imported version?   My only other suggestion would be to add some debugging log functions into the Aura/Tint Healthcolors script so you could at least see if things were getting processed at all. (If you're using the one-click version, you would need to delete it, then re-add it and add 'log' messages where appropriate; of course that's assuming you have enough javascript coding knowledge to be able to do that.) 
1653679286
David M.
Pro
API Scripter
Yeah, I'd check the config. It sounds like either the PC settings have been turned off / altered, or the script possibly has a corrupted State object. From a quick glance at the code, it looks like the script determines the token type is "Player" or "Monster" based on if the character sheet has an explicit entry in the controlledby field.             //**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' : 'aura2'; //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; } Sorry if this has been asked before, but do you get the same behavior in a different game?
Jarren said: Saul J. said: I restarted the sandbox twice. Well that's odd.  Do you get a config menu output when you use the !aura command? It should look something like this: Are you using the script from the one-click, or a modified/imported version?   My only other suggestion would be to add some debugging log functions into the Aura/Tint Healthcolors script so you could at least see if things were getting processed at all. (If you're using the one-click version, you would need to delete it, then re-add it and add 'log' messages where appropriate; of course that's assuming you have enough javascript coding knowledge to be able to do that.)  I do get a config menu. I set "Show Dead PC" to "No"; "PC sees all PC names" to "Yes"; and DeathSFX is blank (it had been set to a sound but the sound never played). This is the one-click version, not a modified/imported version. I could try debugging it and I have enough javascript coding knowledge to do that, but, frankly, I don't have the time or patience during a game and when I experiment during non-game times, it generally works correctly.
David M. said: Yeah, I'd check the config. It sounds like either the PC settings have been turned off / altered, or the script possibly has a corrupted State object. From a quick glance at the code, it looks like the script determines the token type is "Player" or "Monster" based on if the character sheet has an explicit entry in the controlledby field.             //**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' : 'aura2'; //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; } Sorry if this has been asked before, but do you get the same behavior in a different game? I've tried the script in a different game. In fact, I have a "test" game where I test all new scripts before bringing them over to the actual game. It worked fine on the test game. As I said in another message, it tends to work just fine when I'm on alone but fails when anyone else is watching. :-)
1653682622
David M.
Pro
API Scripter
Is "Show PC Health" set to "Yes"? If not, and certain npc tokens have controlledby set (making them treated like PCs by the script), then this might be an issue. If it is set, try toggling off then on again maybe (to re-set the state)? If that doesn't work, then ¯\_(ツ)_/¯
David M. said: Is "Show PC Health" set to "Yes"? If not, and certain npc tokens have controlledby set (making them treated like PCs by the script), then this might be an issue. If it is set, try toggling off then on again maybe (to re-set the state)? If that doesn't work, then ¯\_(ツ)_/¯ "Show PC Health" is set to "Yes". I'll try to toggle it the next time we play and the script doesn't work.
1653709155
Victor B.
Pro
Sheet Author
API Scripter
Saul, you have the first bar set to HP and send to AC, right?  Nothing else?  
1653709180

Edited 1653709215
Victor B.
Pro
Sheet Author
API Scripter
And there's an initial setup for aura/tint and you need to select 5e OGL.  If you aren't sure what you selected, remove aura/tint and add again.  Let it provide options to configure
Victor B. said: Saul, you have the first bar set to HP and send to AC, right?  Nothing else?   Bar 1: HP Bar 2: AC Bar 3: Speed
Victor B. said: And there's an initial setup for aura/tint and you need to select 5e OGL.  If you aren't sure what you selected, remove aura/tint and add again.  Let it provide options to configure Yes, it was set to 5e OGL.
1653880971
Victor B.
Pro
Sheet Author
API Scripter
Alright, PM me.  I'll show you what's wrong and I'll gaurantee that we'll resolve.  You've got something off.