// ChatSetAttr version 0.9
// Last Updated: 2016-08-29
// 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 = '0.9',
feedback = true,
replacers = [ ['<', '[', /',']' , />/g, /\]/g],
['#','|', /#/g, /\|/g],
['~','-', /\~/g, /\-/g],
[';','?', /\;/g, /\?/g],
['`','@', /`/g, /@/g]],
checkInstall = function() {
log(`-=> ChatSetAttr v${version} <=-`);
},
handleError = function(who, errorMsg) {
let output = "/w " + who
+ "
"
+ "
Error
"
+ "
"+errorMsg+"
"
+ "
";
sendChat(who, output);
},
myGetAttrByName = function(charId, attrName, createMissing) {
// Returns attribute object by name
let attr = findObjs({type: 'attribute', characterid: charId, name: attrName}, {caseInsensitive: true})[0];
if (!attr && createMissing) {
attr = createObj('attribute', {characterid: charId, name: attrName});
}
return attr;
},
processInlinerolls = function (msg) {
// Input: msg - chat message
// Output: msg.content, with all inline rolls evaluated
if (_.has(msg, 'inlinerolls')) {
return _.chain(msg.inlinerolls)
.reduce(function(previous, current, index) {
previous['$[[' + index + ']]'] = current.results.total || 0;
return previous;
},{})
.reduce(function(previous, current, index) {
return previous.replace(index, current);
}, msg.content)
.value();
} else {
return msg.content;
}
},
deleteAttributes = function (list, setting) {
let attr;
list.forEach(function(charid) {
_.each(setting, function(attrValue,attrName) {
if (attrName.match(/^repeating_/)) {
attr = getRepeatingAttribute(charid,attrName,false);
if (attr) {
attr.remove();
}
} else {
attr = findObjs({type: 'attribute', characterid: charid, name: attrName}, {caseInsensitive: true});
attr.forEach(a => a.remove());
}
});
});
return;
},
setAttributes = function(list, setting, createMissing) {
// Input: list - array of valid character IDs
// setting - object containing attribute names and desired values
// Output: null. Attribute values are changed.
let attr;
list.forEach(function(charid) {
_.each(setting, function(attrValue,attrName) {
if (attrName.match(/^repeating_/)) {
attr = getRepeatingAttribute(charid,attrName,createMissing);
} else {
attr = myGetAttrByName(charid,attrName,createMissing);
}
if (attr) {
if (attrValue.current !== undefined) attr.set('current',attrValue.current);
if (attrValue.max !== undefined) attr.set('max',attrValue.max);
} else if (!createMissing) {
handleError('GM','Missing attribute '+attrName+' not created for character '+getAttrByName(charid,'character_name')+'.');
} else {
handleError('GM','Repeating attribute '+attrName+' invalid for character '+getAttrByName(charid,'character_name')+'.');
}
});
});
return;
},
getRepeatingAttribute = function(charId, attrName, createMissing) {
let attrMatch = attrName.match(/_\$\d+?_/), attr, attrNameSplit, repSectionIds;
if (attrMatch) {
let rowNum = parseInt(attrMatch[0].replace('$','').replace(/_/g,''));
attrNameSplit = attrName.split(attrMatch[0]);
repSectionIds = _.chain(findObjs({type: 'attribute', characterid: charId}, {caseInsensitive: true}))
.map(a => a.get("name").match('^' + attrNameSplit[0] + '_(-[-A-Za-z0-9]+?)_'))
.compact()
.map(a => a[1])
.uniq()
.value();
if (!_.isUndefined(repSectionIds[rowNum])) {
attr = myGetAttrByName(charId, attrNameSplit[0] + '_' + repSectionIds[rowNum] + '_' + attrNameSplit[1], createMissing);
}
} else {
let idMatch = attrName.match(/_(-[-A-Za-z0-9]+?)_/)[0];
if (idMatch) {
let id = idMatch.replace(/_/g,'');
attrNameSplit = attrName.split(idMatch);
repSectionIds = _.chain(findObjs({type: 'attribute', characterid: charId}, {caseInsensitive: true}))
.map(a => a.get("name").match('^' + attrNameSplit[0] + '_(-[-A-Za-z0-9]+?)_'))
.compact()
.map(a => a[1])
.uniq()
.value();
if (_.contains(repSectionIds, id) ){
attr = myGetAttrByName(charId, attrNameSplit[0] + '_' + id + '_' + attrNameSplit[1], createMissing);
}
}
}
return attr;
},
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
let args, kv, opts = {};
args = _.rest(content.split(/\s+--/));
for (let k in args) {
kv = args[k].split(/\s(.+)/);
if (_.contains(hasValue, kv[0])) {
opts[kv[0]] = kv[1];
} else {
opts[args[k]] = true;
}
}
return opts;
},
parseAttributes = function(args, replace) {
// 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.
args = _.chain(args)
.map(str => str.split(/\s*\|\s*/))
.reject(a => a.length === 0)
.map(sanitizeAttributeArray)
.object()
.value();
if (replace) {
args = _.mapObject(args, function(obj) {
return _.mapObject(obj, function (str) {
for (let rep in replacers) {
str = str.replace(replacers[rep][2],replacers[rep][1]);
}
return str;
});
});
}
return args;
},
sanitizeAttributeArray = function (arr) {
if (arr.length === 1)
return [arr[0],{current : ''}];
if (arr.length === 2)
return [arr[0],{current : arr[1].replace(/^'/,'').replace(/\'$/,'')}];
if (arr.length === 3 && arr[1] === '')
return [arr[0], {max : arr[2].replace(/^'/,'').replace(/\'$/,'')}];
if (arr.length === 3 && arr[1] === "''")
return [arr[0], {current : '', max : arr[2].replace(/^'/,'').replace(/\'$/,'')}];
else if (arr.length === 3)
return [arr[0], {current : arr[1].replace(/^'/,'').replace(/\'$/,''), max : arr[2].replace(/^'/,'').replace(/\'$/,'')}];
if (arr.length > 3) return sanitizeAttributeArray(_.first(arr,3));
},
checkPermissions = function (list, playerid, who) {
let control, character;
for (let k in list) {
character = getObj("character",list[k]);
if (character) {
control = character.get('controlledby').split(/,/);
if(!(playerIsGM(playerid) || _.contains(control,'all') || _.contains(control,playerid))) {
list.splice(k,1);
handleError(who, "Permission error. Name: " + character.get('name'));
}
} else {
handleError(who, "Invalid character id " + list[k]);
list.splice(k,1);
}
}
return list;
},
getIDsFromTokens = function (selected) {
let charIDList = [], characterId, token;
selected.forEach(function(a) {
token = getObj('graphic', a._id);
if (token) {
characterId = token.get("represents");
if (characterId) {
charIDList.push(characterId);
}
}
});
return charIDList;
},
getIDsFromNames = function(charNames, playerid, who) {
let charIDList = _.chain(charNames.split(/\s*,\s*/))
.map(function (n) {
let character = findObjs({type: 'character', name: n}, {caseInsensitive: true})[0];
if (character) return character.id;
else return '';})
.compact()
.value();
return checkPermissions(charIDList, playerid, who);
},
getIDsFromList = function(charid, playerid, who) {
return checkPermissions(charid.split(/\s*,\s*/), playerid, who);
},
sendFeedback = function (who, list, setting, replace) {
let charNames = list.map(id => getAttrByName(id, "character_name")).join(", ");
let values = _.chain(setting).values()
.map(function (o) {
return _.mapObject(o,function (str) {
if (replace) {
for (let rep in replacers) {
str = str.replace(replacers[rep][3],replacers[rep][0]);
}
}
return str;
});})
.map(function (o) {
if (o.max !== undefined && o.current !== undefined) return `${o.current} / ${o.max}`;
if (o.max === undefined) return o.current;
if (o.current === undefined) return `${o.max} (max)`;
return '';})
.value()
.join(", ");
let output = `/w ${who}` +
`` +
`
Setting ${_.keys(setting).join(", ")} to ${values} ` +
`for characters ${charNames}`;
if (replace) {
output += ' (replacing '
+ _.map(replacers, arr => arr[0]).join()
+ ' by '
+ _.map(replacers, arr => arr[1]).join()
+ ')';
}
output += '.
';
sendChat(who, output);
},
sendDeleteFeedback = function (who, list, setting) {
let charNames = list.map(id => getAttrByName(id, "character_name")).join(", ");
let output = `/w ${who}` +
'' +
`
Deleting attributes ${_.keys(setting).join(", ")} ` +
`for characters ${charNames}` +
'.
';
sendChat(who, output);
},
handleInput = function(msg) {
if (msg.type === "api" && msg.content.match(/^!(set|del)attr\b/)) {
// Parsing
let charIDList;
const hasValue = ['charid','name'],
optsArray = ['all','allgm','charid','name','silent','sel','replace', 'nocreate'],
opts = parseOpts(processInlinerolls(msg), hasValue),
setting = parseAttributes(_.chain(opts).omit(optsArray).keys().value(),opts.replace);
if (_.isEmpty(setting)) {
handleError(msg.who, "No attributes supplied.");
return;
}
// Get list of character IDs
if (opts.all && playerIsGM(msg.playerid)) {
charIDList = _.map(findObjs({_type: 'character'}), c => c.id);
} else if (opts.allgm && playerIsGM(msg.playerid)) {
charIDList = _.chain(findObjs({_type: 'character'}))
.filter(c => c.get('controlledby') === '')
.map(c => c.id)
.value();
} else if (opts.charid) {
charIDList = getIDsFromList(opts.charid, msg.playerid, msg.who);
} else if (opts.name) {
charIDList = getIDsFromNames(opts.name, msg.playerid, msg.who);
} else if (opts.sel && msg.selected) {
charIDList = getIDsFromTokens(msg.selected);
} else {
handleError(msg.who,"Don't know what to do.");
return;
}
// Set attributes
if (msg.content.match(/^!delattr\b/)) {
deleteAttributes(charIDList, setting);
if (feedback && !opts.silent && !_.isEmpty(charIDList)) {
sendDeleteFeedback(msg.who, charIDList, setting);
}
} else {
setAttributes(charIDList, setting, !opts.nocreate);
if (feedback && !opts.silent && !_.isEmpty(charIDList)) {
sendFeedback(msg.who, charIDList, setting, opts.replace);
}
}
}
return;
},
registerEventHandlers = function() {
on('chat:message', handleInput);
};
return {
CheckInstall: checkInstall,
RegisterEventHandlers: registerEventHandlers
};
}());
on('ready',function() {
'use strict';
chatSetAttr.CheckInstall();
chatSetAttr.RegisterEventHandlers();
});