Skip to content

Instantly share code, notes, and snippets.

@shdwjk
Last active August 29, 2015 14:04
Show Gist options
  • Save shdwjk/a65bae32d700a00a9c47 to your computer and use it in GitHub Desktop.
Save shdwjk/a65bae32d700a00a9c47 to your computer and use it in GitHub Desktop.
Roll20 API: CharUtils -- A Collection of utilities for manipulating Characters.
// GIST: https://gist.github.com/shdwjk/a65bae32d700a00a9c47
var CharUtils = CharUtils || (function() {
'use strict';
var version = 0.51,
fixNewObj= function(obj) {
var p = obj.changed._fbpath,
new_p = p.replace(/([^\/]*\/){4}/, "/");
obj.fbpath = new_p;
return obj;
},
ch = function (c) {
var entities = {
'<' : 'lt',
'>' : 'gt',
"'" : '#39',
'@' : '#64',
'{' : '#123',
'|' : '#124',
'}' : '#125',
'[' : '#91',
']' : '#93',
'"' : 'quot',
'-' : 'mdash',
' ' : 'nbsp'
};
if(_.has(entities,c) ){
return ('&'+entities[c]+';');
}
return '';
},
showHelp = function() {
sendChat('',
'/w gm '
+'<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">'
+'<div style="font-weight: bold; border-bottom: 1px solid black;font-size: 130%;">'
+'CharUtils v'+version
+'</div>'
+'<div style="padding-left:10px;margin-bottom:3px;">'
+'<p>CharUtils is a collection of utility functions for manipulating characters.</p>'
+'</div>'
+'<b>Commands</b>'
+'<div style="padding-left:10px;">'
+'<b><span style="font-family: serif;">!chardup --'+ch('<')+'Source'+ch('>')+' --'+ch('<')+'Destination'+ch('>')+' [--'+ch('<')+'Destination'+ch('>')+' ... ]</span></b>'
+'<div style="padding-left: 10px;padding-right:20px">'
+'<p>This command requires a minimum of 2 parameters. For all character names, case is ignored and you may use partical names so long as they are unique. For example, '+ch('"')+'King Maximillian'+ch('"')+' could be called '+ch('"')+'max'+ch('"')+' as long as '+ch('"')+'max'+ch('"')+' does not appear in any other names. Exception: An exact match will trump a partial match. In the previous example, if a character named '+ch('"')+'Max'+ch('"')+' existed, it would be the only character matched for <b>--max</b>.</p>'
+'<ul>'
+'<li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">'
+'<b><span style="font-family: serif;">--'+ch('<')+'Source'+ch('>')+'</span></b> '+ch('-')+' This is the name of the character to copy from.'
+'</li> '
+'<li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">'
+'<b><span style="font-family: serif;">--'+ch('<')+'Destination'+ch('>')+'</span></b> '+ch('-')+' This is the name of a character to recieve copies of the abilities from the source. You may specify as many unique names as you like.'
+'</li> '
+'</ul>'
+'</div>'
+'</div>'
+'<div style="padding-left:10px;">'
+'<b><span style="font-family: serif;">!rename-attr --'+ch('<')+'Name'+ch('>')+' --'+ch('<')+'Rename'+ch('>')+' [--'+ch('<')+'Name'+ch('>')+' --'+ch('<')+'Rename'+ch('>')+' ... ]</span></b>'
+'<div style="padding-left: 10px;padding-right:20px">'
+'<p>This command requires an even number of parameters, a minimum of 2 parameters. Each pair of parameters forms a renaming operation. Any attribute with the first name in a pair will be renamed to the second name in the pair.</p>'
+'<p><b>Note:</b> This operation is case sensitive.</p>'
+'<ul>'
+'<li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">'
+'<b><span style="font-family: serif;">--'+ch('<')+'Name'+ch('>')+'</span></b> '+ch('-')+' This is the current name of an attribute.'
+'</li> '
+'<li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">'
+'<b><span style="font-family: serif;">--'+ch('<')+'Rename'+ch('>')+'</span></b> '+ch('-')+' This is the new name for the attribute.'
+'</li> '
+'</ul>'
+'</div>'
+'</div>'
+'<div style="padding-left:10px;">'
+'<b><span style="font-family: serif;">!replace-attr --'+ch('<')+'Name'+ch('>')+' --'+ch('<')+'Replace'+ch('>')+' [--'+ch('<')+'Name'+ch('>')+' --'+ch('<')+'Replace'+ch('>')+' ... ]</span></b>'
+'<div style="padding-left: 10px;padding-right:20px">'
+'<p>This command requires an even number of parameters, a minimum of 2 parameters. Each pair of parameters forms a substituion operation. Any attribute reference with the first name in a pair will be replaced by the second name in the pair for all abilities.</p>'
+'<p><b>Note:</b> This operation is case sensitive.</p>'
+'<ul>'
+'<li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">'
+'<b><span style="font-family: serif;">--'+ch('<')+'Name'+ch('>')+'</span></b> '+ch('-')+' This is the current name of an attribute.'
+'</li> '
+'<li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">'
+'<b><span style="font-family: serif;">--'+ch('<')+'Replace'+ch('>')+'</span></b> '+ch('-')+' This is the new name for the attribute.'
+'</li> '
+'</ul>'
+'</div>'
+'</div>'
+'</div>'
);
},
attributeReplace = function(renameMap) {
var AttrByName = {},
AttrByChar = {},
CharByID = {},
AbilityByCharID = {},
Subs = [];
_.map(findObjs({
type: 'attribute'
}),function(a){
if(!_.has(AttrByName,a.get('name'))){
AttrByName[a.get('name')]=[a];
} else {
AttrByName[a.get('name')].push(a);
}
if(!_.has(AttrByChar,a.get('characterid'))){
AttrByChar[a.get('characterid')]=[a];
} else {
AttrByChar[a.get('characterid')].push(a);
}
});
_.map(findObjs({
type: 'ability'
}),function(a){
if(!_.has(AbilityByCharID,a.get('characterid'))){
AbilityByCharID[a.get('characterid')]=[a];
} else {
AbilityByCharID[a.get('characterid')].push(a);
}
});
_.map(findObjs({
type: 'character'
}),function(c){
if(!_.has(CharByID,c.get('id'))){
CharByID[c.get('id')]=c;
}
});
_.each(renameMap, function(to,from){
_.each(AttrByName[to], function(a) {
if(_.has(CharByID,a.get('characterid'))) {
Subs=_.reduce([
CharByID[a.get('characterid')].get('id')+"\\|",
CharByID[a.get('characterid')].get('name')+"\\|",
"selected\\|",
"target\\|",
''
], function(memo,name){
var r1=new RegExp('@{' + name + from + '}','gi'),
r2=new RegExp('@{' + name + from + "\\|max}",'gi');
memo.push([r1,'@{' + name + to + '}']);
memo.push([r2,'@{' + name + to + "\\|max}"]);
return memo;
},[]);
_.each(AbilityByCharID[a.get('characterid')], function (ab) {
var text=ab.get('action'),
orig=text;
text=_.reduce(Subs,function(text,sp){
return text.replace(sp[0],sp[1]);
},text);
if(text!==orig) {
ab.set({action: text});
}
});
}
});
});
},
attributeRename = function(renameMap) {
var AttrByName = [],
AttrByChar = [];
_.map(findObjs({
type: 'attribute'
}),function(a){
if(!_.has(AttrByName,a.get('name'))){
AttrByName[a.get('name')]=[a];
} else {
AttrByName[a.get('name')].push(a);
}
if(!_.has(AttrByChar,a.get('characterid'))){
AttrByChar[a.get('characterid')]=[a];
} else {
AttrByChar[a.get('characterid')].push(a);
}
});
_.each(renameMap, function(to,from){
_.each(AttrByName[from], function(a) {
var match=_.find(AttrByChar[a.get('characterid')],function(attr) {
return attr.get('name') === to;
});
if(match) {
match.set({
current: a.get('current'),
max: a.get('max')
});
a.set({
name: 'DELETE ME'
});
} else {
a.set({
name: to
});
}
});
});
},
HandleInput = function(msg) {
var args,
chars,
matches,
abilities,
errors=[];
if (msg.type !== "api" || !isGM(msg.playerid) ) {
return;
}
args = msg.content.split(" --");
switch(args[0]) {
case '!replace-attr':
if( 1 === args.length || 0 === args.length % 2 ) {
showHelp();
break;
}
attributeReplace(_.chain(args)
.rest()
.reduce(function(m,v,k){
var nkey=Math.floor(k/2);
if ( _.has(m,nkey) ) {
m[nkey].push(v);
} else {
m[nkey]=[v];
}
return m;
},[])
.reduce(function(m,e){
m[e[0]] = e[1];
return m;
},{})
.value());
break;
case '!rename-attr':
if( 1 === args.length || 0 === args.length % 2 ) {
showHelp();
break;
}
attributeRename(_.chain(args)
.rest()
.reduce(function(m,v,k){
var nkey=Math.floor(k/2);
if ( _.has(m,nkey) ) {
m[nkey].push(v);
} else {
m[nkey]=[v];
}
return m;
},[])
.reduce(function(m,e){
m[e[0]] = e[1];
return m;
},{})
.value());
break;
case '!chardup':
if(args.length < 3) {
if(args.length !== 1) {
sendChat('','/w gm '
+'<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">'
+'<span style="font-weight:bold;color:#990000;">Error:</span> '
+'Please specify at least 2 characters.'
+'</div>'
);
}
showHelp();
break;
}
chars=findObjs({type: 'character',archived: false});
matches=_.chain(args)
.rest()
.map(function(n){
var l=_.filter(chars,function(c){
return c.get('name').toLowerCase() === n.toLowerCase();
});
return ( 1 === l.length ? l : _.filter(chars,function(c){
return -1 !== c.get('name').toLowerCase().indexOf(n.toLowerCase());
}));
})
.value();
_.each(matches,function(o,idx){
if(1 !== o.length) {
if(o.length) {
errors.push('Character [<b>'+args[idx+1]+'</b>] is ambiguous and matches '+o.length+' names: <b><i> '+_.map(o,function(e){
return e.get('name');
}).join(', ')+'</i></b>');
} else {
errors.push('Character [<b>'+args[idx+1]+'</b>] does not match any names.');
}
}
},errors);
if(errors.length) {
sendChat('','/w gm '
+'<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">'
+'<div><span style="font-weight:bold;color:#990000;">Error:</span> '
+errors.join('</div><div><span style="font-weight:bold;color:#990000;">Error:</span> ')
+'</div>'
+'</div>'
);
break;
}
if( ! errors.length) {
matches=_.flatten(matches,true);
abilities=findObjs({type: 'ability', characterid: matches[0].id});
if(!abilities.length) {
sendChat('','/w gm Character [<b>'+matches[0].get('name')+'</b>] does not have any abilities.');
break;
}
_.each(abilities,function(a){
_.chain(matches)
.rest()
.each(function(c){
fixNewObj(createObj('ability',{
characterid: c.id,
name: a.get('name'),
description: a.get('description'),
action: a.get('action'),
istokenaction: a.get('istokenaction')
}));
});
});
sendChat('','/w gm '
+'<div style="border: 1px solid black; background-color: white; padding: 3px 3px;">'
+'<div>Source Character: <b>'+matches[0].get('name')+'</b></div>'
+'<div>Abilities:'
+'<ul><li><i>'+_.map(abilities,function(a){
return a.get('name');
}).join('</i></li><li><i>')
+'</i></li></ul>'
+'</div>'
+'<div>Destination Character'+( 2 === matches.length ?'':'s')+':'
+'<ul><li><b>'+_.map(_.rest(matches),function(c){
return c.get('name');
}).join('</b></li><li><b>')
+'</b></li></ul>'
+'</div>'
+'</div>'
);
}
break;
}
},
RegisterEventHandlers = function() {
on('chat:message', HandleInput);
};
return {
RegisterEventHandlers: RegisterEventHandlers
};
}());
on("ready",function(){
'use strict';
var Has_IsGM=false;
try {
_.isFunction(isGM);
Has_IsGM=true;
}
catch (err)
{
log('--------------------------------------------------------------');
log('CharUtils requires the isGM module to work.');
log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625');
log('--------------------------------------------------------------');
}
if( Has_IsGM )
{
CharUtils.RegisterEventHandlers();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment