// ChatSetAttr version 1.7
// Last Updated: 2017-11-09
// A script to create, modify, or delete character attributes from the chat area or macros.
// If you don't like my choices for --replace, you can edit the replacers variable at your own peril to change them.
var chatSetAttr = chatSetAttr || (function () {
'use strict';
const version = '1.7',
schemaVersion = 3,
replacers = [
[//g, ']'],
[/\\rbrak/g, ']'],
[/\;/g, '?'],
[/\\ques/g, '?'],
[/`/g, '@'],
[/\\at/g, '@'],
[/\~/g, '-'],
[/\\n/g, '\n'],
],
// Basic Setup
checkInstall = function () {
log(`-=> ChatSetAttr v${version} <=-`);
if (!state.ChatSetAttr || state.ChatSetAttr.version !== schemaVersion) {
log(` > Updating ChatSetAttr Schema to v${schemaVersion} <`);
state.ChatSetAttr = {
version: schemaVersion,
globalconfigCache: {
lastsaved: 0
},
playersCanModify: false,
playersCanEvaluate: false,
useWorkers: true
};
}
checkGlobalConfig();
},
checkGlobalConfig = function () {
const s = state.ChatSetAttr,
g = globalconfig && globalconfig.chatsetattr;
if (g && g.lastsaved && g.lastsaved > s.globalconfigCache.lastsaved) {
log(' > Updating ChatSetAttr from Global Config < [' +
(new Date(g.lastsaved * 1000)) + ']');
s.playersCanModify = 'playersCanModify' === g['Players can modify all characters'];
s.playersCanEvaluate = 'playersCanEvaluate' === g['Players can use --evaluate'];
s.useWorkers = 'useWorkers' === g['Trigger sheet workers when setting attributes'];
s.globalconfigCache = globalconfig.chatsetattr;
}
},
// Utility functions
isDef = function (value) {
return value !== undefined;
},
getWhisperPrefix = function (playerid) {
const player = getObj('player', playerid);
if (player && player.get('_displayname')) {
return '/w "' + player.get('_displayname') + '" ';
}
else {
return '/w GM ';
}
},
sendChatMessage = function (msg, from) {
if (from === undefined) from = 'ChatSetAttr';
sendChat(from, msg, null, {
noarchive: true
});
},
setAttribute = function (attr, value) {
state.ChatSetAttr.useWorkers ? attr.setWithWorker(value) : attr.set(value);
},
handleErrors = function (whisper, errors) {
if (errors.length) {
const output = whisper +
'
' +
'
Errors
' +
`
${errors.join('
')}
` +
'
';
sendChatMessage(output);
errors.splice(0, errors.length);
}
},
showConfig = function (whisper) {
const optionsText = [{
name: 'playersCanModify',
command: 'players-can-modify',
desc: 'Determines if players can use --name and --charid to ' +
'change attributes of characters they do not control.'
}, {
name: 'playersCanEvaluate',
command: 'players-can-evaluate',
desc: 'Determines if players can use the --evaluate option. ' +
'Be careful in giving players access to this option, because ' +
'it potentially gives players access to your full API sandbox.'
}, {
name: 'useWorkers',
command: 'use-workers',
desc: 'Determines if setting attributes should trigger sheet worker operations.'
}].map(getConfigOptionText).join(''),
output = whisper + 'ChatSetAttr Configuration' +
'
!setattr-config can be invoked in the following format:
!setattr-config --option
' +
'
Specifying an option toggles the current setting. There are currently two' +
' configuration options:
' + optionsText + '
';
sendChatMessage(output);
},
getConfigOptionText = function (o) {
const button = state.ChatSetAttr[o.name] ?
'ON' :
'OFF';
return '' +
'- ' +
'
${button}
` +
`${o.command}${htmlReplace('-')}` +
`${o.desc}
${o.name} is currently ${button}` +
`
Toggle `;
},
getCharNameById = function (id) {
const character = getObj('character', id);
return (character) ? character.get('name') : '';
},
escapeRegExp = function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
htmlReplace = function (str) {
const entities = {
'<': 'lt',
'>': 'gt',
"'": '#39',
'*': '#42',
'@': '#64',
'{': '#123',
'|': '#124',
'}': '#125',
'[': '#91',
']': '#93',
'_': '#95',
'"': 'quot'
};
return String(str).split('').map(c => (entities[c]) ? ('&' + entities[c] + ';') : c).join('');
},
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;
}
},
notifyAboutDelay = function (whisper) {
const chatFunction = () => sendChatMessage(whisper + 'Your command is taking a ' +
'long time to execute. Please be patient, the process will finish eventually.');
return setTimeout(chatFunction, 8000);
},
getCIKey = function (obj, name) {
const nameLower = name.toLowerCase();
let result = false;
Object.entries(obj).forEach(([k, v]) => {
if (k.toLowerCase() === nameLower) {
result = k;
}
});
return result;
},
generateUUID = function () {
"use strict";
var a = 0,
b = [];
return function () {
var c = (new Date()).getTime() + 0,
d = c === a;
a = c;
for (var e = new Array(8), f = 7; 0 <= f; f--) {
e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64);
c = Math.floor(c / 64);
}
c = e.join("");
if (d) {
for (f = 11; 0 <= f && 63 === b[f]; f--) {
b[f] = 0;
}
b[f]++;
}
else {
for (f = 0; 12 > f; f++) {
b[f] = Math.floor(64 * Math.random());
}
}
for (f = 0; 12 > f; f++) {
c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]);
}
return c;
};
}(),
generateRowID = function () {
"use strict";
return generateUUID().replace(/_/g, "Z");
},
// Setting attributes happens in a delayed recursive way to prevent the sandbox
// from overheating.
delayedGetAndSetAttributes = function (whisper, list, setting, errors, rData, opts) {
const timeNotification = notifyAboutDelay(whisper),
cList = [].concat(list),
feedback = [],
dWork = function (charid) {
const attrs = getCharAttributes(charid, setting, errors, rData, opts);
setCharAttributes(charid, setting, errors, feedback, attrs, opts);
if (cList.length) {
setTimeout(dWork, 50, cList.shift());
}
else {
clearTimeout(timeNotification);
if (!opts.mute) handleErrors(whisper, errors);
if (!opts.silent) sendFeedback(whisper, feedback, opts);
}
}
dWork(cList.shift());
},
setCharAttributes = function (charid, setting, errors, feedback, attrs, opts) {
const charFeedback = {};
Object.entries(attrs).forEach(([attrName, attr]) => {
let newValue;
charFeedback[attrName]= {};
const fillInAttrs = setting[attrName].fillin,
settingValue = _.pick(setting[attrName], ['current', 'max']);
if (opts.reset) {
newValue = {
current: attr.get('max')
};
}
else {
newValue = (fillInAttrs) ?
_.mapObject(settingValue, v => fillInAttrValues(charid, v)) : Object.assign({}, settingValue);
}
if (opts.evaluate) {
try {
newValue = _.mapObject(newValue, function (v) {
const parsed = eval(v);
if (_.isString(parsed) || Number.isFinite(parsed) || _.isBoolean(parsed)) {
return parsed.toString();
}
else return v;
});
}
catch (err) {
errors.push(`Something went wrong with --evaluate` +
` for the character ${getCharNameById(charid)}.` +
` You were warned. The error message was: ${err}.` +
` Attribute ${attrName} left unchanged.`);
return;
}
}
if (opts.mod || opts.modb) {
Object.entries(newValue).forEach(([k, v]) => {
let moddedValue = parseFloat(v) + parseFloat(attr.get(k) || '0');
if (!_.isNaN(moddedValue)) {
if (opts.modb && k === 'current') {
moddedValue = Math.min(Math.max(moddedValue, 0), parseFloat(attr.get('max')) || Infinity);
}
newValue[k] = moddedValue;
}
else {
delete newValue[k];
const type = (k === 'max') ? 'maximum ' : '';
errors.push(`Attribute ${type}${attrName} is not number-valued for ` +
`character ${getCharNameById(charid)}. Attribute ${type}left unchanged.`);
}
});
}
newValue = _.mapObject(newValue, v => String(v));
charFeedback[attrName] = newValue;
setAttribute(attr, newValue);
});
// Feedback
if (!opts.silent) {
if ('fb-content' in opts) {
const finalFeedback = Object.entries(setting).reduce((m, [attrName, value], k) => {
return m.replace(`_NAME${k}_`, attrName)
.replace(`_TCUR${k}_`, htmlReplace(value.current || ''))
.replace(`_TMAX${k}_`, htmlReplace(value.max || ''))
.replace(`_CUR${k}_`, htmlReplace(charFeedback[attrName].current || ''))
.replace(`_MAX${k}_`, htmlReplace(charFeedback[attrName].max || ''));
}, String(opts['fb-content']).replace('_CHARNAME_', getCharNameById(charid)))
.replace(/_(?:TCUR|TMAX|CUR|MAX|NAME)\d*_/g, '');
feedback.push(finalFeedback);
}
else {
const finalFeedback = Object.entries(charFeedback).map(([k, o]) => {
if ('max' in o && 'current' in o)
return `${k} to ${htmlReplace(o.current) || '(empty)'} / ${htmlReplace(o.max) || '(empty)'}`;
else if ('current' in o) return `${k} to ${htmlReplace(o.current) || '(empty)'}`;
else if ('max' in o) return `${k} to ${htmlReplace(o.max) || '(empty)'} (max)`;
else return null;
}).filter(x => !!x).join(', ').replace(/\n/g, '
');
if (finalFeedback.length) {
feedback.push(`Setting ${finalFeedback} for character ${getCharNameById(charid)}.`);
}
else {
feedback.push(`Nothing to do for character ${getCharNameById(charid)}.`);
}
}
}
return;
},
fillInAttrValues = function (charid, expression) {
let match = expression.match(/%(\S.*?)(?:_(max))?%/),
replacer;
while (match) {
replacer = getAttrByName(charid, match[1], match[2] || 'current') || '';
expression = expression.replace(/%(\S.*?)(?:_(max))?%/, replacer);
match = expression.match(/%(\S.*?)(?:_(max))?%/);
}
return expression;
},
// Getting attributes for a specific character
getCharAttributes = function (charid, setting, errors, rData, opts) {
const standardAttrNames = Object.keys(setting).filter(x => !setting[x].repeating),
rSetting = _.omit(setting, standardAttrNames);
return Object.assign({},
getCharStandardAttributes(charid, standardAttrNames, errors, opts),
getCharRepeatingAttributes(charid, rSetting, errors, rData, opts)
);
},
getCharStandardAttributes = function (charid, attrNames, errors, opts) {
const attrs = {},
attrNamesUpper = attrNames.map(x => x.toUpperCase());
if (attrNames.length === 0) return {};
findObjs({
_type: 'attribute',
_characterid: charid
}).forEach(attr => {
const nameIndex = attrNamesUpper.indexOf(attr.get('name').toUpperCase());
if (nameIndex !== -1) attrs[attrNames[nameIndex]] = attr;
});
_.difference(attrNames, Object.keys(attrs)).forEach(key => {
if (!opts.nocreate && !opts.deletemode) {
attrs[key] = createObj('attribute', {
characterid: charid,
name: key
});
}
else if (!opts.deletemode) {
errors.push(`Missing attribute ${key} not created for` +
` character ${getCharNameById(charid)}.`);
}
});
return attrs;
},
getCharRepeatingAttributes = function (charid, setting, errors, rData, opts) {
const allRepAttrs = {},
attrs = {},
repRowIds = {},
repOrders = {};
if (rData.sections.size === 0) return {};
rData.sections.forEach(prefix => allRepAttrs[prefix] = {});
// Get attributes
findObjs({
_type: 'attribute',
_characterid: charid
}).forEach(o => {
const attrName = o.get('name');
rData.sections.forEach((prefix, k) => {
if (attrName.search(rData.regExp[k]) === 0) {
allRepAttrs[prefix][attrName] = o;
}
else if (attrName === '_reporder_' + prefix) {
repOrders[prefix] = o.get('current').split(',');
}
});
});
// Get list of repeating row ids by prefix from allRepAttrs
rData.sections.forEach((prefix, k) => {
repRowIds[prefix] = [...new Set(Object.keys(allRepAttrs[prefix])
.map(n => n.match(rData.regExp[k]))
.filter(x => !!x)
.map(a => a[1]))];
if (repOrders[prefix]) {
repRowIds[prefix] = _.chain(repOrders[prefix])
.intersection(repRowIds[prefix])
.union(repRowIds[prefix])
.value();
}
});
const repRowIdsLo = _.mapObject(repRowIds, l => l.map(n => n.toLowerCase()));
rData.toCreate.forEach(prefix => repRowIds[prefix].push(generateRowID()));
Object.entries(setting).forEach(([attrName, value]) => {
const p = value.repeating;
let finalId;
if (isDef(p.rowNum) && isDef(repRowIds[p.splitName[0]][p.rowNum])) {
finalId = repRowIds[p.splitName[0]][p.rowNum];
}
else if (p.rowIdLo === '-create' && !opts.deletemode) {
finalId = repRowIds[p.splitName[0]][repRowIds[p.splitName[0]].length - 1];
}
else if (isDef(p.rowIdLo) && repRowIdsLo[p.splitName[0]].includes(p.rowIdLo)) {
finalId = repRowIds[p.splitName[0]][repRowIdsLo[p.splitName[0]].indexOf(p.rowIdLo)];
}
else if (isDef(p.rowNum)) {
errors.push(`Repeating row number ${p.rowNum} invalid for` +
` character ${getCharNameById(charid)}` +
` and repeating section ${p.splitName[0]}.`);
}
else {
errors.push(`Repeating row id ${p.rowIdLo} invalid for` +
` character ${getCharNameById(charid)}` +
` and repeating section ${p.splitName[0]}.`);
}
if (finalId && p.rowMatch) {
const repRowUpper = (p.splitName[0] + '_' + finalId).toUpperCase();
Object.entries(allRepAttrs[p.splitName[0]]).forEach(([name, attr]) => {
if (name.toUpperCase().indexOf(repRowUpper) === 0) {
attrs[name] = attr;
}
});
}
else if (finalId) {
const finalName = p.splitName[0] + '_' + finalId + '_' + p.splitName[1],
attrNameCased = getCIKey(allRepAttrs[p.splitName[0]], finalName);
if (attrNameCased) {
attrs[attrName] = allRepAttrs[p.splitName[0]][attrNameCased];
}
else if (!opts.nocreate && !opts.deletemode) {
attrs[attrName] = createObj('attribute', {
characterid: charid,
name: finalName
});
}
else if (!opts.deletemode) {
errors.push(`Missing attribute ${finalName} not created` +
` for character ${getCharNameById(charid)}.`);
}
}
});
return attrs;
},
// Deleting attributes
delayedDeleteAttributes = function (whisper, list, setting, errors, rData, opts) {
const timeNotification = notifyAboutDelay(whisper),
cList = [].concat(list),
feedback = {},
dWork = function (charid) {
const attrs = getCharAttributes(charid, setting, errors, rData, opts);
feedback[charid] = [];
deleteCharAttributes(charid, attrs, feedback);
if (cList.length) {
setTimeout(dWork, 50, cList.shift());
}
else {
clearTimeout(timeNotification);
if (!opts.silent) sendDeleteFeedback(whisper, feedback, opts);
}
}
dWork(cList.shift());
},
deleteCharAttributes = function (charid, attrs, feedback) {
Object.keys(attrs).forEach(name => {
attrs[name].remove();
feedback[charid].push(name);
});
},
// These functions parse the chat input.
parseOpts = function (content, hasValue) {
// Input: content - string of the form command --opts1 --opts2 value --opts3.
// values come separated by whitespace.
// hasValue - array of all options which come with a value
// Output: object containing key:true if key is not in hasValue. and containing
// key:value otherwise
return content.replace(/
/g, '') // delete added HTML line breaks
.replace(/\s+$/g, '') // delete trailing whitespace
.replace(/\s*{{((?:.|\n)*)\s+}}$/, ' $1') // replace content wrapped in curly brackets
.replace(/\\([{}])/g, '$1') // add escaped brackets
.split(/\s+--/)
.slice(1)
.reduce((m, arg) => {
const kv = arg.split(/\s(.+)/);
if (hasValue.includes(kv[0])) {
m[kv[0]] = kv[1] || '';
}
else {
m[arg] = true;
}
return m;
}, {});
},
parseAttributes = function (args, opts, errors) {
// Input: args - array containing comma-separated list of strings, every one of which contains
// an expression of the form key|value or key|value|maxvalue
// replace - true if characters from the replacers array should be replaced
// Output: Object containing key|value for all expressions.
const globalRepeatingData = {
regExp: new Set(),
toCreate: new Set(),
sections: new Set(),
},
setting = args.map(str => {
return str.split(/(\\?(?:#|\|))/g)
.reduce((m, s) => {
if ((s === '#' || s === '|')) m[m.length] = '';
else if ((s === '\\#' || s === '\\|')) m[m.length - 1] += s.slice(-1);
else m[m.length - 1] += s;
return m;
}, ['']);
})
.filter(v => !!v)
// Replace for --replace
.map(arr => {
return arr.map((str, k) => {
if (opts.replace && k > 0) return replacers.reduce((m, rep) => m.replace(rep[0], rep[1]), str);
else return str;
});
})
// parse out current/max value
.map(arr => {
const value = {};
if (arr.length < 3 || arr[1] !== '') {
value.current = (arr[1] || '').replace(/^'((?:.|\n)*)'$/, '$1');
}
if (arr.length > 2) {
value.max = arr[2].replace(/^'((?:.|\n)*)'$/, '$1');
}
return [arr[0], value];
})
// Find out if we need to run %_% replacement
.map(([name, value]) => {
if ((value.current && value.current.search(/%(\S.*?)(?:_(max))?%/) !== -1) ||
(value.max && value.max.search(/%(\S.*?)(?:_(max))?%/) !== -1)) value.fillin = true;
else value.fillin = false;
return [name, value];
})
// Do repeating section stuff
.map(([name, value]) => {
if (name.search(/^repeating_/) === 0) {
value.repeating = getRepeatingData(name, globalRepeatingData, opts, errors);
}
else value.repeating = false;
return [name, value];
})
.filter(([name, value]) => value.repeating !== null)
.reduce((p, c) => {
p[c[0]] = Object.assign(p[c[0]] || {}, c[1])
return p;
}, {});
globalRepeatingData.sections.forEach(s => {
globalRepeatingData.regExp.add(new RegExp(`^${escapeRegExp(s)}_(-[-A-Za-z0-9]+?|\\d+)_`, 'i'));
});
globalRepeatingData.regExp = [...globalRepeatingData.regExp];
globalRepeatingData.toCreate = [...globalRepeatingData.toCreate];
globalRepeatingData.sections = [...globalRepeatingData.sections];
return [setting, globalRepeatingData];
},
getRepeatingData = function (name, globalData, opts, errors) {
const match = name.match(/_(\$\d+|-[-A-Za-z0-9]+|\d+)(_)?/);
let output = {};
if (match && match[1][0] === '$' && match[2] === '_') {
output.rowNum = parseInt(match[1].slice(1));
}
else if (match && match[2] === '_') {
output.rowId = match[1];
output.rowIdLo = match[1].toLowerCase();
}
else if (match && match[1][0] === '$' && opts.deletemode) {
output.rowNum = parseInt(match[1].slice(1));
output.rowMatch = true;
}
else if (match && opts.deletemode) {
output.rowId = match[1];
output.rowIdLo = match[1].toLowerCase();
output.rowMatch = true;
}
else {
errors.push(`Could not understand repeating attribute name ${name}.`);
output = null;
}
if (output) {
output.splitName = name.split(match[0]);
globalData.sections.add(output.splitName[0]);
if (output.rowIdLo === '-create' && !opts.deletemode) {
globalData.toCreate.add(output.splitName[0]);
}
}
return output;
},
// These functions are used to get a list of character ids from the input,
// and check for permissions.
checkPermissions = function (list, errors, playerid, isGM) {
return list.filter(id => {
const character = getObj('character', id);
if (character) {
const control = character.get('controlledby').split(/,/);
if (!(isGM || control.includes('all') || control.includes(playerid) || state.ChatSetAttr.playersCanModify)) {
errors.push(`Permission error for character ${character.get('name')}.`);
return false;
}
else return true;
}
else {
errors.push(`Invalid character id ${id}.`);
return false;
}
});
},
getIDsFromTokens = function (selected) {
return (selected || []).map(obj => getObj('graphic', obj._id))
.filter(x => !!x)
.map(token => token.get('represents'))
.filter(id => getObj('character', id || ''));
},
getIDsFromNames = function (charNames, errors) {
return charNames.split(/\s*,\s*/)
.map(name => {
const character = findObjs({
_type: 'character',
name: name
}, {
caseInsensitive: true
})[0];
if (character) {
return character.id;
}
else {
errors.push(`No character named ${name} found.`);
return null;
}
})
.filter(x => !!x);
},
sendFeedback = function (whisper, feedback, opts) {
const output = (opts['fb-public'] ? '' : whisper) +
'' +
'
' + (('fb-header' in opts) ? opts['fb-header'] : 'Setting attributes') + '
' +
'
' + (feedback.join('
') || 'Nothing to do.') + '
';
sendChatMessage(output, opts['fb-from']);
},
sendDeleteFeedback = function (whisper, feedback, opts) {
let output = (opts['fb-public'] ? '' : whisper) +
'' +
'
' + (('fb-header' in opts) ? opts['fb-header'] : 'Deleting attributes') + '
';
output += Object.entries(feedback)
.filter(([charid, arr]) => arr.length)
.map(([charid, arr]) => `Deleting attribute(s) ${arr.join(', ')} for character ${getCharNameById(charid)}.`)
.join('
') || 'Nothing to do.';
output += '
';
sendChatMessage(output, opts['fb-from']);
},
// Main function, called after chat message input
handleInput = function (msg) {
if (msg.type !== 'api') return;
const mode = msg.content.match(/^!(reset|set|del|mod|modb)attr\b(?:-|\s|$)(config)?/),
whisper = getWhisperPrefix(msg.playerid);
if (mode && mode[2]) {
if (playerIsGM(msg.playerid)) {
const opts = parseOpts(msg.content, []);
if (opts['players-can-modify']) {
state.ChatSetAttr.playersCanModify = !state.ChatSetAttr.playersCanModify;
}
if (opts['players-can-evaluate']) {
state.ChatSetAttr.playersCanEvaluate = !state.ChatSetAttr.playersCanEvaluate;
}
if (opts['use-workers']) {
state.ChatSetAttr.useWorkers = !state.ChatSetAttr.useWorkers;
}
showConfig(whisper);
}
}
else if (mode) {
// Parsing input
let charIDList = [],
errors = [];
const hasValue = ['charid', 'name', 'fb-header', 'fb-content', 'fb-from'],
optsArray = ['all', 'allgm', 'charid', 'name', 'allplayers', 'sel', 'deletemode',
'replace', 'nocreate', 'mod', 'modb', 'evaluate', 'silent', 'reset', 'mute',
'fb-header', 'fb-content', 'fb-from', 'fb-public'
],
opts = parseOpts(processInlinerolls(msg), hasValue),
isGM = msg.playerid === 'API' || playerIsGM(msg.playerid);
opts.mod = opts.mod || (mode[1] === 'mod');
opts.modb = opts.modb || (mode[1] === 'modb');
opts.reset = opts.reset || (mode[1] === 'reset');
opts.silent = opts.silent || opts.mute;
opts.deletemode = (mode[1] === 'del');
// Sanitise feedback
if ('fb-from' in opts) opts['fb-from'] = String(opts['fb-from']);
// Parse desired attribute values
const [setting, rData] = parseAttributes(Object.keys(_.omit(opts, optsArray)), opts, errors);
// Fill in header info
if ('fb-header' in opts) {
opts['fb-header'] = Object.entries(setting).reduce((m, [n, v], k) => {
return m.replace(`_NAME${k}_`, n)
.replace(`_TCUR${k}_`, htmlReplace(v.current || ''))
.replace(`_TMAX${k}_`, htmlReplace(v.max || ''));
}, String(opts['fb-header'])).replace(/_(?:TCUR|TMAX|NAME)\d*_/g, '');
}
if (opts.evaluate && !isGM && !state.ChatSetAttr.playersCanEvaluate) {
if (!opts.mute) handleErrors(whisper, ['The --evaluate option is only available to the GM.']);
return;
}
// Get list of character IDs
if (opts.all && isGM) {
charIDList = findObjs({
_type: 'character'
}).map(c => c.id);
}
else if (opts.allgm && isGM) {
charIDList = findObjs({
_type: 'character'
}).filter(c => c.get('controlledby') === '')
.map(c => c.id);
}
else if (opts.allplayers && isGM) {
charIDList = findObjs({
_type: 'character'
}).filter(c => c.get('controlledby') !== '')
.map(c => c.id);
}
else {
if (opts.charid) charIDList.push(...opts.charid.split(/\s*,\s*/));
if (opts.name) charIDList.push(...getIDsFromNames(opts.name, errors));
if (opts.sel) charIDList.push(...getIDsFromTokens(msg.selected));
charIDList = checkPermissions([...new Set(charIDList)], errors, msg.playerid, isGM);
}
if (charIDList.length === 0) {
errors.push('No target characters. You need to supply one of --all, --allgm, --sel,' +
' --allplayers, --charid, or --name.');
}
if (Object.keys(setting).length === 0) {
errors.push('No attributes supplied.');
}
// Get attributes
if (!opts.mute) handleErrors(whisper, errors);
// Set or delete attributes
if (charIDList.length > 0 && Object.keys(setting).length > 0) {
if (opts.deletemode) {
delayedDeleteAttributes(whisper, charIDList, setting, errors, rData, opts);
}
else {
delayedGetAndSetAttributes(whisper, charIDList, setting, errors, rData, opts);
}
}
}
return;
},
registerEventHandlers = function () {
on('chat:message', handleInput);
};
return {
CheckInstall: checkInstall,
RegisterEventHandlers: registerEventHandlers
};
}());
on('ready', function () {
'use strict';
chatSetAttr.CheckInstall();
chatSetAttr.RegisterEventHandlers();
});