/* global createObj TokenMod spawnFxWithDefinition getObj state playerIsGM sendChat _ findObjs log on*/
/*
My Profile link: https://app.roll20.net/users/262130/dxwarlock
GIT link: https://github.com/dxwarlock/Roll20/blob/master/Public/HeathColors
Roll20Link: https://app.roll20.net/forum/post/4630083/script-aura-slash-tint-healthcolor
*/
/*jshint bitwise: false*/
var HealthColors = HealthColors || (function () {
'use strict';
var version = '1.6.0',
ScriptName = "HealthColors",
schemaVersion = '1.0.3',
Updated = "Feb 12 2019",
/*------------------------
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);
//**SPURT FX------------//
if(curValue != prevValue && prevValue != "" && update !== "YES") {
//CHECK BLOOD ATTRIB------------
var UseBlood;
if(oCharacter !== undefined) {
UseBlood = lookupUseBlood(oCharacter);
}
if(state.HealthColors.FX === true && obj.get("layer") == "objects" && (UseBlood !== "OFF" || UseBlood !== "NO")) {
var HurtColor, HealColor, FX, aFX, FXArray = [];
var amount = Math.abs(curValue - prevValue);
var HitSizeCalc = Math.min((amount / maxValue) * 4, 1);
var Scale = obj.get("height") / 70;
var HitSize = Math.max(HitSizeCalc, 0.2) * (_.random(60, 100) / 100);
//IF HEALED------------
if(curValue > prevValue) {
aFX = findObjs({_type: "custfx",name: '-DefaultHeal'}, {caseInsensitive: true})[0];
FX = aFX.get("definition");
HealColor = HEXtoRGB(state.HealthColors.HealFX);
FX.startColour = HealColor;
FXArray.push(FX);
}
//IF HURT------------
else if(curValue < prevValue) {
aFX = findObjs({_type: "custfx",name: '-DefaultHurt'}, {caseInsensitive: true})[0];
if(aFX) FX = aFX.get("definition");
//CHECK DEFAULT COLOR--
if(UseBlood === "DEFAULT" || UseBlood === undefined) {
HurtColor = HEXtoRGB(state.HealthColors.HurtFX);
FX.startColour = HurtColor;
FXArray.push(FX);
}
//ELSE CHECK CUSTOM COLOR/FX--
else if(UseBlood !== "DEFAULT" && UseBlood !== undefined) {
HurtColor = HEXtoRGB(UseBlood);
//IF CUSTOM COLOR--
if(_.difference(HurtColor, [0, 0, 0, 0]).length !== 0) {
FX.startColour = HurtColor;
FXArray.push(FX);
}
//ELSE ASSUME CUSTOM FX--
else {
var i = UseBlood.split(/,/);
_.each(i, function (FXname) {
aFX = findObjs({_type: "custfx",name: FXname}, {caseInsensitive: true})[0];
if(aFX) FXArray.push(aFX.get("definition"));
else GMW("No FX with name " + FXname);
});
}
}
}
else return;
//SPAWN FX------------
_.each(FXArray, function (FX) {
SpawnFX(Scale, HitSize, obj.get("left"), obj.get("top"), FX, 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];
UPPER = UPPER.toUpperCase();
state.HealthColors.HealFX = UPPER;
break;
case "HURT":
UPPER = msgFormula[2];
UPPER = UPPER.toUpperCase();
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 && update === "YES") {
obj.set({'aura1_color': "transparent",'aura2_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,
'aura2_radius': sizeSet * scale * 0.1,
'aura1_color': markerColor,
'aura2_color': pColor,
'showplayers_aura1': true,
'showplayers_aura2': true,
});
}
},
//REMOVE ALL------------
SetAuraNone = function (obj) {
if(state.HealthColors.auraTint === true) obj.set({'tint_color': "transparent",});
else obj.set({'aura1_color': "transparent",'aura2_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 + "
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+'
');
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;";
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;';
var off = "#A84D4D";
var disable = "#D6D6D6";
var HR = "