Here's a script that will watch for a player's color to be changed to/from transparent. It will watch for any API commands and report if any commands happened within the past 5 seconds, which could help determine if that is causing the player's color is changing. Edit: I updated the script to 1.1 with a little more robust error checking and a nicer interface. var PlayerColorWatcher = PlayerColorWatcher || (function () {
'use strict';
var version = '1.1', schemaVersion = 1;
var defaults = {
intervalSeconds: 1,
whisperTarget: 'gm',
chatMode: 'all',
maxRecentCommands: 20
};
var css = {
window: 'background:#d9ecff;border:2px solid #000;border-radius:6px;padding:6px;line-height:1.35;text-align:left;',
title: 'font-weight:bold;text-align:center;margin:0 0 0 0;',
button: 'display:inline-block;margin:-2px 0;padding:0px 4px;border:1px solid #000;border-radius:5px;background:#fff;color:#003366;font-weight:bold;text-decoration:none;',
buttons: 'white-space:normal;text-align:left;',
code: 'font-family:monospace;background:#fff;border:1px solid #999;border-radius:3px;padding:0 3px;'
};
var recentCommands = [], timer = null;
var normalizeIntervalSeconds = function (value) {
value = parseFloat(value);
if (isNaN(value)) value = defaults.intervalSeconds;
return Math.max(1, Math.min(10, Math.round(value)));
};
var normalizeChatMode = function (value) {
value = String(value || '').toLowerCase();
if (value === 'all' || value === 'trackall' || value === 'everything') return 'all';
if (value === 'transparent' || value === 'tofromtransparent' || value === 'transparentonly') return 'transparent';
if (value === 'logonly' || value === 'none' || value === 'off') return 'logonly';
return defaults.chatMode;
};
var checkInstall = function () {
state.PlayerColorWatcher = state.PlayerColorWatcher || {};
state.PlayerColorWatcher.version = schemaVersion;
state.PlayerColorWatcher.config = state.PlayerColorWatcher.config || {};
state.PlayerColorWatcher.players = state.PlayerColorWatcher.players || {};
Object.keys(defaults).forEach(function (key) {
if (typeof state.PlayerColorWatcher.config[key] === 'undefined') state.PlayerColorWatcher.config[key] = defaults[key];
});
if (typeof state.PlayerColorWatcher.config.intervalMs !== 'undefined') {
state.PlayerColorWatcher.config.intervalSeconds = Math.round(state.PlayerColorWatcher.config.intervalMs / 1000);
delete state.PlayerColorWatcher.config.intervalMs;
}
if (typeof state.PlayerColorWatcher.config.chatMode === 'undefined') {
state.PlayerColorWatcher.config.chatMode = state.PlayerColorWatcher.config.logOnlyTransparentChanges ? 'transparent' :
state.PlayerColorWatcher.config.logAllChanges === false ? 'logonly' : 'all';
}
delete state.PlayerColorWatcher.config.logAllChanges;
delete state.PlayerColorWatcher.config.logOnlyTransparentChanges;
delete state.PlayerColorWatcher.config.includeRecentCommands;
state.PlayerColorWatcher.config.intervalSeconds = normalizeIntervalSeconds(state.PlayerColorWatcher.config.intervalSeconds);
state.PlayerColorWatcher.config.chatMode = normalizeChatMode(state.PlayerColorWatcher.config.chatMode);
};
var getConfig = function () { return state.PlayerColorWatcher.config; };
var getRecentCommandSeconds = function () { return getConfig().intervalSeconds * 6; };
var chatName = function () { return 'Player Color Watcher v' + version; };
var htmlEscape = function (str) {
return String(str || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
};
var logEscape = function (str) { return String(str || '').replace(/\s+/g, ' ').trim(); };
var makeButton = function (label, command) { return '<a style="' + css.button + '" href="' + htmlEscape(command) + '">' + label + '</a>'; };
var makeWindow = function (body) { return '<br><div style="' + css.window + '">' + body + '</div>'; };
var whisper = function (body) { sendChat(chatName(), '/w ' + getConfig().whisperTarget + ' ' + makeWindow(body)); };
var makeTitle = function (title) { return '<div style="' + css.title + '">' + htmlEscape(title) + '</div>'; };
var makeTitleWithButton = function (title, button) {
return '<div style="text-align:center;font-weight:bold;margin:0 0 2px 0;">' +
'<span style="float:right;font-weight:normal;">' + button + '</span>' +
'<span>' + htmlEscape(title) + '</span></div><div style="clear:both;"></div>';
};
var getPlayerName = function (player) { return player.get('_displayname') || player.get('displayname') || player.id; };
var colorIsTransparent = function (color) { return String(color || '').toLowerCase() === 'transparent'; };
var shouldSendChatForChange = function (oldColor, newColor) {
var mode = getConfig().chatMode;
if (mode === 'all') return true;
if (mode === 'transparent') return colorIsTransparent(oldColor) || colorIsTransparent(newColor);
return false;
};
var getChatModeLabel = function () {
var mode = getConfig().chatMode;
if (mode === 'all') return 'All';
if (mode === 'transparent') return 'Transparent';
if (mode === 'logonly') return 'Log Only';
return mode;
};
var addRecentCommand = function (msg) {
var cfg = getConfig();
recentCommands.push({ time: Date.now(), who: msg.who || 'Unknown', playerid: msg.playerid || '', content: msg.content || '' });
while (recentCommands.length > cfg.maxRecentCommands) recentCommands.shift();
};
var getRecentNonWatcherCommands = function () {
var now = Date.now(), recentCommandSeconds = getRecentCommandSeconds();
return recentCommands.filter(function (cmd) {
return now - cmd.time <= recentCommandSeconds * 1000 && !String(cmd.content || '').match(/^!pcw\b/);
});
};
var getApiLogNote = function () {
var matches = getRecentNonWatcherCommands(), cmd;
if (!matches.length) return 'no api used';
cmd = matches[matches.length - 1];
return 'api: ' + logEscape(cmd.who) + ': ' + logEscape(cmd.content);
};
var getRecentCommandSummary = function () {
var matches = getRecentNonWatcherCommands(), recentCommandSeconds = getRecentCommandSeconds();
if (!matches.length) return '<br><b>Recent API commands:</b> none in the last ' + recentCommandSeconds + ' sec';
return '<br><b>Recent API commands:</b><br>' + matches.map(function (cmd) {
return '&bull; ' + htmlEscape(cmd.who) + ': <span style="' + css.code + '">' + htmlEscape(cmd.content) + '</span>';
}).join('<br>');
};
var initializePlayers = function () {
var stored = state.PlayerColorWatcher.players;
findObjs({ _type: 'player' }).forEach(function (player) {
stored[player.id] = { name: getPlayerName(player), color: player.get('color') };
});
};
var checkPlayers = function () {
var stored = state.PlayerColorWatcher.players;
findObjs({ _type: 'player' }).forEach(function (player) {
var id = player.id, name = getPlayerName(player), currentColor = player.get('color'), previousColor;
if (!stored[id]) {
stored[id] = { name: name, color: currentColor };
return;
}
previousColor = stored[id].color;
if (previousColor !== currentColor) {
stored[id] = { name: name, color: currentColor };
log('Player Color Watcher: ' + name + ' color changed from ' + previousColor + ' to ' + currentColor +
' | chat mode: ' + getChatModeLabel() + ' | ' + getApiLogNote());
if (shouldSendChatForChange(previousColor, currentColor)) {
whisper(
makeTitleWithButton('Color Changed', makeButton('⚙️', '!pcw')) +
'<b>' + htmlEscape(name) + '</b> color: ' +
'<b>' + htmlEscape(previousColor) + '</b> to ' +
'<b>' + htmlEscape(currentColor) + '</b>' +
getRecentCommandSummary()
);
}
}
});
};
var showSettings = function () {
var cfg = getConfig();
whisper(
makeTitle('Settings') +
'<b>Chat Mode:</b> ' +
makeButton(getChatModeLabel(), '!pcw --mode|?{Chat output mode|All,all|Transparent,transparent|Log Only,logonly}') +
'<br><b>Interval:</b> ' +
makeButton('every ' + cfg.intervalSeconds + ' sec', '!pcw --interval|?{Polling interval in seconds, 1-10|' + cfg.intervalSeconds + '}') +
'<br><b>Recent API Commands:</b> last ' + getRecentCommandSeconds() + ' sec'
);
};
var setIntervalSeconds = function (value) {
getConfig().intervalSeconds = normalizeIntervalSeconds(value);
startTimer();
showSettings();
};
var setChatMode = function (value) {
getConfig().chatMode = normalizeChatMode(value);
showSettings();
};
var getPipedArgs = function (content) {
var args = {};
(content.match(/--[^|]+(?:\|[^\s]+)?/g) || []).forEach(function (part) {
var pieces = part.replace(/^--/, '').split('|');
args[String(pieces[0] || '').toLowerCase()] = pieces.slice(1).join('|');
});
return args;
};
var handleInput = function (msg) {
var args;
if (msg.type === 'api') addRecentCommand(msg);
if (msg.type !== 'api' || !msg.content.match(/^!pcw\b/)) return;
if (!playerIsGM(msg.playerid)) {
sendChat(chatName(), '/w "' + msg.who + '" ' + makeWindow(makeTitle('Permission Denied') + 'Only the GM can use this command.'));
return;
}
args = getPipedArgs(msg.content);
if (Object.keys(args).length === 0) {
showSettings();
return;
}
if (typeof args.mode !== 'undefined' || typeof args.chatmode !== 'undefined') {
setChatMode(typeof args.mode !== 'undefined' ? args.mode : args.chatmode);
return;
}
if (typeof args.interval !== 'undefined') {
setIntervalSeconds(args.interval);
return;
}
showSettings();
};
var startTimer = function () {
var cfg = getConfig();
if (timer) clearInterval(timer);
timer = setInterval(checkPlayers, cfg.intervalSeconds * 1000);
};
var registerEventHandlers = function () { on('chat:message', handleInput); };
on('ready', function () {
checkInstall();
initializePlayers();
registerEventHandlers();
startTimer();
log('Player Color Watcher v' + version + ' ready.');
});
return {};
}());