// Github: https://github.com/shdwjk/Roll20API/blob/master/Torch/Torch.js
// By: The Aaron, Arcane Scriptomancer
// Contact: https://app.roll20.net/users/104025/the-aaron
var Torch = Torch || (function() {
'use strict';
var version = '0.8.8',
lastUpdate = 1476099723,
schemaVersion = 0.1,
flickerURL = 'https://s3.amazonaws.com/files.d20.io/images/4277467/iQYjFOsYC5JsuOPUCI9RGA/thumb.png?1401938659',
flickerPeriod = 400,
flickerDeltaLocation = 2,
flickerDeltaRadius = 0.1,
flickerDeltaAngle = 5,
flickerInterval = false,
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(who) {
sendChat('',
'/w "'+who+'" '+
'
'+
'
'+
'Torch v'+version+
'
'+
'
'+
'
Torch provides commands for managing dynamic lighting. Supplying a first argument of help to any of the commands displays this help message, as will calling !torch or !snuff with nothing supplied or selected.
'+
'
Torch now supports Jack Taylor inspired flickering lights. Flicker lights are only active on pages where a player is (GMs, drag yourself to other pages if you don'+ch("'")+'t want to move the party.) and are persisted in the state. Flicker lights can be used in addition to regular lights as they are implemented on a separate invisible token that follows the nomal token.
'+
'
'+
'
Commands'+
'
'+
'
!torch '+ch('[')+ch('<')+'Radius'+ch('>')+' '+ch('[')+ch('<')+'Dim Start'+ch('>')+' '+ch('[')+ch('<')+'All Players'+ch('>')+' '+ch('[')+ch('<')+'Token ID'+ch('>')+ch('|')+ch('<')+'--Angle'+ch('>')+' ... '+ch(']')+ch(']')+ch(']')+ch(']')+''+
'
'+
'
Sets the light for the selected/supplied tokens. Only GMs can supply token ids to adjust.
'+
'
Note: If you are using multiple '+ch('@')+ch('{')+'target'+ch('|')+'token_id'+ch('}')+' calls in a macro, and need to adjust light on fewer than the supplied number of arguments, simply select the same token several times. The duplicates will be removed.
'+
'
'+
'- '+
''+ch('<')+'Radius'+ch('>')+' '+ch('-')+' The radius that the light extends to. (Default: 40)'+
'
'+
'- '+
''+ch('<')+'Dim Start'+ch('>')+' '+ch('-')+' The radius at which the light begins to dim. (Default: Half of Radius )'+
'
'+
'- '+
''+ch('<')+'All Players'+ch('>')+' '+ch('-')+' Should all players see the light, or only the controlling players (Darkvision, etc). Specify one of 1, on, yes, true, sure, yup, or - for yes, anything else for no. (Default: yes)'+
'
'+
'- '+
''+ch('<')+'Token ID'+ch('>')+' '+ch('-')+' A Token ID, usually supplied with something like '+ch('@')+ch('{')+'target'+ch('|')+'Target 1'+ch('|')+'token_id'+ch('}')+'.'+
'
'+
'- '+
''+ch('<')+'--Angle'+ch('>')+' '+ch('-')+' The angle of the light arc of the light. (Default: 360)'+
'
'+
'
'+
'
'+
'
!snuff '+ch('[')+ch('<')+'Token ID'+ch('>')+' ... '+ch(']')+''+
'
'+
'
Turns off light for the selected/supplied tokens. Only GMs can supply token ids to adjust.
'+
'
Note: If you are using multiple '+ch('@')+ch('{')+'target'+ch('|')+'token_id'+ch('}')+' calls in a macro, and need to adjust light on fewer than the supplied number of arguments, simply select the same token several times. The duplicates will be removed.
'+
'
'+
'- '+
''+ch('<')+'Token ID'+ch('>')+' '+ch('-')+' A Token ID, usually supplied with something like '+ch('@')+ch('{')+'target'+ch('|')+'Target 1'+ch('|')+'token_id'+ch('}')+'.'+
'
'+
'
'+
'
'+
'
!flicker-on '+ch('[')+ch('<')+'Radius'+ch('>')+' '+ch('[')+ch('<')+'Dim Start'+ch('>')+' '+ch('[')+ch('<')+'All Players'+ch('>')+' '+ch('[')+ch('<')+'Token ID'+ch('>')+ch('|')+ch('<')+'--Angle'+ch('>')+' ... '+ch(']')+ch(']')+ch(']')+ch(']')+''+
'
'+
'
Behaves identically to !torch, save that it creates a flickering light.
'+
'
'+
'
!flicker-off '+ch('[')+ch('<')+'Token ID'+ch('>')+' ... '+ch(']')+''+
'
'+
'
Behaves identically to !snuff, save that it affects the flickering light.
'+
'
'+
'
!daytime '+ch('[')+ch('<')+'Token ID'+ch('>')+ch(']')+''+
'
'+
'
Turns off dynamic lighting for the current player page, or the page of the selected/supplied token.
'+
'
'+
'- '+
''+ch('<')+'Token ID'+ch('>')+' '+ch('-')+' A Token ID, usually supplied with something like '+ch('@')+ch('{')+'target'+ch('|')+'Target 1'+ch('|')+'token_id'+ch('}')+'.'+
'
'+
'
'+
'
'+
'
!nighttime '+ch('[')+ch('<')+'Token ID'+ch('>')+ch(']')+''+
'
'+
'
Turns on dynamic lighting for the current player page, or the page of the selected/supplied token.
'+
'
'+
'- '+
''+ch('<')+'Token ID'+ch('>')+' '+ch('-')+' A Token ID, usually supplied with something like '+ch('@')+ch('{')+'target'+ch('|')+'Target 1'+ch('|')+'token_id'+ch('}')+'.'+
'
'+
'
'+
'
'+
'
!global-light '+ch('[')+ch('<')+'Token ID'+ch('>')+ch(']')+''+
'
'+
'
Toggles Global Illumination for the current player page, or the page of the selected/supplied token.
'+
'
'+
'- '+
''+ch('<')+'Token ID'+ch('>')+' '+ch('-')+' A Token ID, usually supplied with something like '+ch('@')+ch('{')+'target'+ch('|')+'Target 1'+ch('|')+'token_id'+ch('}')+'.'+
'
'+
'
'+
'
'+
'
'+
'
'
);
},
setFlicker = function(o,r,d,p,a) {
var found = _.findWhere(state.Torch.flickers, {parent: o.id}),
fobj;
if( found ) {
fobj = getObj('graphic',found.id);
if(fobj) {
fobj.set({
layer: 'walls',
showname: false,
aura1_radius: '',
showplayers_aura1: false,
light_radius: r,
light_dimradius: d,
light_otherplayers: p,
light_angle: a
});
} else {
delete state.Torch.flickers[found.id];
}
}
if(!fobj) {
// new flicker
fobj =createObj('graphic',{
imgsrc: flickerURL,
subtype: 'token',
name: 'Flicker',
pageid: o.get('pageid'),
width: 70,
height: 70,
top: o.get('top'),
left: o.get('left'),
layer: 'walls',
light_radius: r,
light_dimradius: d,
light_otherplayers: p,
light_angle: a
});
}
toBack(fobj);
state.Torch.flickers[fobj.id]={
id: fobj.id,
parent: o.id,
active: true,
page: o.get('pageid'),
light_radius: r,
light_dimradius: d,
light_angle: a
};
},
clearFlicker = function(fid) {
var f = getObj('graphic',fid);
if(f) {
f.remove();
}
delete state.Torch.flickers[fid];
},
handleInput = function(msg) {
var args, radius, dim_radius, arc_angle=360, other_players, page, obj, objs=[],who;
if (msg.type !== "api") {
return;
}
who=getObj('player',msg.playerid).get('_displayname');
args = msg.content.split(" ");
switch(args[0]) {
case '!torch':
if((args[1]||'').match(/^(--)?help$/) || ( !_.has(msg,'selected') && args.length < 5)) {
showHelp(who);
return;
}
radius = parseInt(args[1],10) || 40;
dim_radius = parseInt(args[2],10) || (radius/2);
other_players = _.contains([1,'1','on','yes','true','sure','yup','-'], args[3] || 1 );
objs = _.chain(args)
.rest(4)
.uniq()
.filter(function(a){
var angle=a.match(/^--(\d+)$/);
if(angle){
arc_angle=(Math.min(360,Math.max(0,angle[1])));
return false;
}
return true;
})
.map(function(t){
return getObj('graphic',t);
})
.reject(()=>playerIsGM(msg.playerid))
.reject(_.isUndefined)
.value();
_.each(_.union(objs,_.map(msg.selected,function (o) {
return getObj(o._type,o._id);
})), function(o){
o.set({
light_radius: radius,
light_dimradius: dim_radius,
light_otherplayers: other_players,
light_angle: arc_angle
});
});
break;
case '!snuff':
if((args[1]||'').match(/^(--)?help$/) || ( !_.has(msg,'selected') && args.length < 2)) {
showHelp(who);
return;
}
if(playerIsGM(msg.playerid)) {
_.chain(args)
.rest(1)
.uniq()
.map(function(t){
return getObj('graphic',t);
})
.reject(_.isUndefined)
.each(function(t) {
t.set({
light_radius: '',
light_dimradius: '',
light_otherplayers: false,
light_angle: 360
});
});
}
_.each(msg.selected,function (o) {
getObj(o._type,o._id).set({
light_radius: '',
light_dimradius: '',
light_otherplayers: false,
light_angle: 360
});
});
break;
case '!daytime':
if((args[1]||'').match(/^(--)?help$/) ) {
showHelp(who);
return;
}
if(playerIsGM(msg.playerid)) {
if(msg.selected) {
obj=getObj('graphic', msg.selected[0]._id);
} else if(args[1]) {
obj=getObj('graphic', args[1]);
}
page = getObj('page', (obj && obj.get('pageid')) || Campaign().get('playerpageid'));
if(page) {
page.set({
showlighting: false
});
sendChat('','/w gm It is now Daytime on '+page.get('name')+'!');
}
}
break;
case '!nighttime':
if((args[1]||'').match(/^(--)?help$/) ) {
showHelp(who);
return;
}
if(playerIsGM(msg.playerid)) {
if(msg.selected) {
obj=getObj('graphic',msg.selected[0]._id);
} else if(args[1]) {
obj=getObj('graphic', args[1]);
}
page = getObj('page', (obj && obj.get('pageid')) || Campaign().get('playerpageid'));
if(page) {
page.set({
showlighting: true
});
sendChat('','/w gm It is now Nighttime on '+page.get('name')+'!');
}
}
break;
case '!global-light':
if((args[1]||'').match(/^(--)?help$/) ) {
showHelp(who);
return;
}
if(playerIsGM(msg.playerid)) {
if(msg.selected) {
obj=getObj('graphic', msg.selected[0]._id);
} else if(args[1]) {
obj=getObj('graphic', args[1]);
}
page = getObj('page', (obj && obj.get('pageid')) || Campaign().get('playerpageid'));
if(page) {
page.set({
lightglobalillum: !(page.get('lightglobalillum'))
});
sendChat('','/w gm Global Illumination is now '+(page.get('lightglobalillum')?'ON':'OFF' )+' on page '+page.get('name')+'!');
}
}
break;
case '!flicker-on':
if((args[1]||'').match(/^(--)?help$/) || ( !_.has(msg,'selected') && args.length < 5)) {
showHelp(who);
return;
}
radius = parseInt(args[1],10) || 40;
dim_radius = parseInt(args[2],10) || (radius/2);
other_players = _.contains([1,'1','on','yes','true','sure','yup','-'], args[3] || 1 );
objs=_.chain(args)
.rest(4)
.uniq()
.filter(function(a){
var angle=a.match(/^--(\d+)$/);
if(angle){
arc_angle=(Math.min(360,Math.max(0,angle[1])));
return false;
}
return true;
})
.reject(()=>playerIsGM(msg.playerid))
.map(function(t){
return getObj('graphic',t);
})
.reject(_.isUndefined)
.value();
_.each(_.union(objs,_.map(msg.selected,function (o) {
return getObj(o._type,o._id);
})), function(o){
setFlicker(o, radius, dim_radius, other_players,arc_angle);
});
break;
case '!flicker-off':
if((args[1]||'').match(/^(--)?help$/) || ( !_.has(msg,'selected') && args.length < 2)) {
showHelp(who);
return;
}
if(playerIsGM(msg.playerid)) {
objs=_.chain(args)
.rest(1)
.uniq()
.value();
}
objs=_.union(objs,_.pluck(msg.selected,'_id'));
_.each(state.Torch.flickers, function(f) {
if( _.contains(objs, f.parent)) {
clearFlicker(f.id);
}
});
break;
}
},
animateFlicker = function() {
var pages = _.union([Campaign().get('playerpageid')], _.values(Campaign().get('playerspecificpages')));
_.chain(state.Torch.flickers)
.where({active:true})
.filter(function(o){
return _.contains(pages,o.page);
})
.each(function(fdata){
var o = getObj('graphic',fdata.parent),
f = getObj('graphic',fdata.id),
dx, dy, dr, da;
if(!o) {
clearFlicker(fdata.id);
} else {
if(!f) {
delete state.Torch.flickers[fdata.id];
} else {
dx = randomInteger(2 * flickerDeltaLocation)-flickerDeltaLocation;
dy = randomInteger(2 * flickerDeltaLocation)-flickerDeltaLocation;
dr = randomInteger(2 * (fdata.light_radius*flickerDeltaRadius)) - (fdata.light_radius*flickerDeltaRadius);
da = randomInteger(2 * flickerDeltaAngle)-flickerDeltaAngle;
f.set({
top: o.get('top')+dy,
left: o.get('left')+dx,
light_radius: fdata.light_radius+dr,
light_angle: ((360 === fdata.light_angle) ? (360) : (Math.min(360,Math.max(fdata.light_angle+da,0)))) || 360
});
}
}
});
},
handleTokenDelete = function(obj) {
var found = _.findWhere(state.Torch.flickers, {parent: obj.id});
if(found) {
clearFlicker(found.id);
} else {
found = _.findWhere(state.Torch.flickers, {id: obj.id});
if(found) {
delete state.Torch.flickers[obj.id];
}
}
},
checkInstall = function() {
log('-=> Torch v'+version+' <=- ['+(new Date(lastUpdate*1000))+']');
if( ! _.has(state,'Torch') || state.Torch.version !== schemaVersion) {
log(' > Updating Schema to v'+schemaVersion+' <');
/* Default Settings stored in the state. */
state.Torch = {
version: schemaVersion,
flickers: {}
};
}
flickerInterval = setInterval(animateFlicker,flickerPeriod);
},
registerEventHandlers = function() {
on('chat:message', handleInput);
on('destroy:graphic', handleTokenDelete);
};
return {
CheckInstall: checkInstall,
RegisterEventHandlers: registerEventHandlers
};
}());
on("ready",function(){
'use strict';
Torch.CheckInstall();
Torch.RegisterEventHandlers();
});