Finderski, this sounded like a fun project so I took a swing at it. This script ties the token tooltip to one or more character attributes and updates the tooltip when attributes are changed or added. How it works: Add a line in the gmnotes field of a token that represents a character. It should be its own line, with a format like this: tooltip: someValue where someValue is a string that contains one or more references to an attribute that exists on that character. Examples: tooltip: @{myAttr} //will add the value of the "myAttr" attribute to the tooltip tooltip: hp:@{hp}/@{hp|max} //if hp were current=7 and max=22, the tooltip would read "hp:7/22". Note everything but the attributes are just treated as text Since the gmnotes of the token is used, you could have multiple tokens representing the same character display different tooltips. Note 1: If the script is unable to find the [case-sensitive] attribute listed in gmnotes, it will display "undefined" in the place of the attribute in the tooltip string. E.g. if you wrote "tooltip: hp:@{hp}/@hp|maxgfkj}" it would result in a tooltip of "hp:7/undefined" Note 2: the script currently only updates the tooltip when attributes are changed or added. The easiest way to trigger the initial update is if you have a linked token bar value. Change it once and back again to trigger the tooltip. I should probably put in a manual command, but it's really just a proof-of-concept script at the moment. Note 3: the script registers itself to the ChatSetAttr and token-mod scripts, so changes made to attributes or linked bar values from either of these scripts will trigger the tooltip update. If these scripts are not installed, you will get a chat message when the sandbox loads and tooltips will only update when manually changed. Note 4: the api does not alter the "show tooltip" checkbox, but will honor whatever setting the token has Note 5: In my admittedly minimal testing, I haven't noticed an uptick in lag when creating characters, etc., but this is entirely possible. This is the first time I have done anything with attribute events. There could also be horrible conflicts that have yet to be discovered. If you start noticing issues, just delete it and burn it with fire! Here's the script: const ToolTipAttr = (() => {
const scriptName = "ToolTipAttr";
const version = '0.1';
const checkInstall = () => {
log(scriptName + ' v' + version + ' initialized.');
};
const handleAttrChange = function(obj, prev) {
const decodeUnicode = (str) => str.replace(/%u[0-9a-fA-F]{2,4}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
let charID = obj.get('_characterid');
let char = getObj('character', charID);
if (char) {
let toks = findObjs({
_type: 'graphic',
represents: charID
});
if (toks.length > 0) {
let attrs = findObjs({
_type: 'attribute',
_characterid: charID
});
if (attrs) {
toks.forEach (tok => {
let gmNotes = unescape(decodeUnicode(tok.get('gmnotes')));
let notesArr = gmNotes.split('</p>').map(e => e.replace('<p>',''));
if (notesArr) {
let tooltipLine = notesArr.filter(s => s.toLowerCase().includes('tooltip:'))[0];
if (tooltipLine) {
let placeholderArr = tooltipLine.match(/@{(.*?)}/g); //returns array [@{attr1}, @{attr2}...]
let tooltipStr = tooltipLine.split('tooltip:')[1].trim(); //returns the structure of the text to be in tooltip, with placeholders e.g. "@{hp} / @{hp|max}"
let attrNames = tooltipStr.match(/(?<=\{).+?(?=\})/g); //returns an array of attributeNames between {}, e.g. [hp, hp|max]
if(attrNames) {
attrNames.forEach((name, i) => {
let currentOrMax = 'current';
if (name.toLowerCase().indexOf('|max') > 0) {
name = name.toLowerCase().split('|max')[0];
currentOrMax = 'max';
}
let attribute = attrs.filter(a => a.get('name')===name);
if (attribute.length > 0) {
tooltipStr = tooltipStr.replace(placeholderArr[i], attribute[0].get(currentOrMax) || 'undefined');
} else {
tooltipStr = tooltipStr.replace(placeholderArr[i], 'undefined');
}
});
tok.set('tooltip', tooltipStr);
} else {
tok.set('tooltip', 'undefined');
}
}
}
});
}
}
}
}
const registerEventHandlers = () => {
//manual change of attribute
on('change:attribute', handleAttrChange);
on('add:attribute', handleAttrChange);
//register this script to ChatSetAttr
try {
ChatSetAttr.registerObserver('change', handleAttrChange);
} catch (error) {
sendChat(scriptName, `/w gm Unable to register ${scriptName} to ChatSetAttr. Install ChatSetAttr for full functionality`);
}
//register this script to TokenMod to handle linked bars/attributes
if('undefined' !== typeof TokenMod && TokenMod.ObserveTokenChange){
TokenMod.ObserveTokenChange(function(obj,prev){
let attr;
if(obj.get('bar1_value') !== prev.bar1_value && obj.get('bar1_link') !== '') {
attr = getObj('attribute', obj.get('bar1_link'));
} else if(obj.get('bar2_value') !== prev.bar2_value && obj.get('bar2_link') !== '') {
attr = getObj('attribute', obj.get('bar2_link'));
} else if(obj.get('bar3_value') !== prev.bar3_value && obj.get('bar3_link') !== '') {
attr = getObj('attribute', obj.get('bar3_link'));
}
if (attr) {
handleAttrChange(attr, {})
}
});
} else {
sendChat(scriptName, `/w gm Unable to register ${scriptName} to Token-mod. Install Token-mod for full functionality`);
}
};
on('ready', () => {
checkInstall();
registerEventHandlers();
});
})();