// Github: https://github.com/shdwjk/Roll20API/blob/master/DryErase/DryErase.js // By: The Aaron, Arcane Scriptomancer // Contact: https://app.roll20.net/users/104025/the-aaron var DryErase = DryErase || (function() { 'use strict'; var version = '0.1.8', lastUpdate = 1490368977, schemaVersion = 0.3, clearURL = 'https://s3.amazonaws.com/files.d20.io/images/4277467/iQYjFOsYC5JsuOPUCI9RGA/thumb.png?1401938659', defaults = { css: { button: { 'border': '1px solid #cccccc', 'border-radius': '1em', 'background-color': '#006dcc', 'margin': '0 .1em', 'font-weight': 'bold', 'padding': '.1em 1em', 'color': 'white' } } }, templates = {}, buildTemplates = function() { templates.cssProperty =_.template( '<%=name %>: <%=value %>;' ); templates.style = _.template( 'style="<%='+ '_.map(css,function(v,k) {'+ 'return templates.cssProperty({'+ 'defaults: defaults,'+ 'templates: templates,'+ 'name:k,'+ 'value:v'+ '});'+ '}).join("")'+ ' %>"' ); templates.button = _.template( ' href="<%= command %>"><%= label||"Button" %>' ); }, makeButton = function(command, label, backgroundColor, color){ return templates.button({ command: command, label: label, templates: templates, defaults: defaults, css: { color: color, 'background-color': backgroundColor } }); }, parseExistingPaths = function(){ _.each(filterObjs(function(o){ return 'path'===o.get('type') && !_.has(state.DryErase.drawings,o.id) && 'objects' === o.get('layer'); }),handlePathDraw); }, cleanupObjectReferences = function(){ var ids = _.keys(state.DryErase.drawings); filterObjs(function(o){ ids=_.without(ids,o.id); return false; }); _.each(ids,eraseDrawing); }, checkInstall = function() { log('-=> DryErase v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); if( ! _.has(state,'DryErase') || state.DryErase.version !== schemaVersion) { log(' > Updating Schema to v'+schemaVersion+' <'); switch(state.DryErase && state.DryErase.version) { case 0.2: state.DryErase.config.labelGranted = false; /* break; // intentional dropthrough */ case 'UpdateSchemaVersion': state.DryErase.version = schemaVersion; break; default: state.DryErase = { version: schemaVersion, config: { report: true, autoHide: true, autoDelete: false, labelGranted: false }, drawings: {}, grantedPlayers: [] }; break; } } buildTemplates(); cleanupObjectReferences(); parseExistingPaths(); }, 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 ''; }, makeConfigOption = function(config,command,text) { var onOff = (config ? 'On' : 'Off' ), color = (config ? '#5bb75b' : '#faa732' ); return '
'+ text+ '
'+ makeButton(command,onOff,color)+ '
'+ '
'+ '
'; }, getConfigOption_Report = function() { return makeConfigOption( state.DryErase.config.report, '!dry-erase-config --toggle-report', 'Report notifies the GM via a whisper when a player that has not been granted drawing permission makes a drawing. ' ); }, getConfigOption_LabelGranted = function() { return makeConfigOption( state.DryErase.config.labelGranted, '!dry-erase-config --toggle-label-granted', 'Label Granted Player Drawings adds a GM Layer highlight with the player name to drawings made by granted players.' ); }, getConfigOption_AutoHide = function() { return makeConfigOption( state.DryErase.config.autoHide, '!dry-erase-config --toggle-auto-hide', 'Auto Hide automatically moves to the GM Layer any drawings by players that have not been granted drawing permission.' ); }, getConfigOption_AutoDelete = function() { return makeConfigOption( state.DryErase.config.autoDelete, '!dry-erase-config --toggle-auto-delete', 'Auto Delete automatically deletes drawings by players that have not been granted drawing permission.' ); }, getAllConfigOptions = function() { return getConfigOption_AutoDelete() + getConfigOption_AutoHide() + getConfigOption_Report() + getConfigOption_LabelGranted(); }, getPlayerGrantOption = function (player) { var p=(_.has(player,'id') ? player : getObj('player',player)),button; if(p){ button = (playerIsGM(p.id) ? makeButton('','GM','black','red') : ( _.contains(state.DryErase.grantedPlayers,p.id) ? makeButton('!dry-erase --revoke-playerid '+p.id,'Revoke','#faa732') : makeButton('!dry-erase --grant-playerid '+p.id, 'Grant') ) ); return '
'+ p.get('displayname')+ ''+ '
'+ button+ '
'+ '
'+ '
'; } return ''; }, getAllPlayerGrantOptions = function(){ return '
'+ _.map(findObjs({ type: 'player' }),getPlayerGrantOption).join('')+ '
'; }, showHelp = function(who) { sendChat('','/w "'+who+'" ' +'
' +'
' +'DryErase v'+version +'
' +'
' +'

DryErase gives you control over how your players are drawing on your maps.

' +'
' +'Commands' +'
' +'!dry-erase [--help]' +'
' +'

Shows the help as well as the configuration and player options.

' +'
    ' +'
  • ' +'--help '+ch('-')+' Shows the Help screen' +'
  • ' +'
' +'
' +'
' +'Player Permissions' +getAllPlayerGrantOptions() +'Configuration' +getAllConfigOptions() +'
' ); }, allowDrawing = function(id){ var details, path, highlight; if(_.has(state.DryErase.drawings,id)){ details = state.DryErase.drawings[id]; path = getObj('path',details.path); highlight = getObj('graphic',details.highlight); if(path && 'gmlayer' === path.get('layer')){ path.set({layer: 'objects'}); } if(!state.DryErase.config.labelGranted){ delete state.DryErase.drawings[details.highlight]; if(highlight){ highlight.remove(); } } } }, eraseDrawing = function(id){ var details, path, highlight; if(_.has(state.DryErase.drawings,id)){ details = state.DryErase.drawings[id]; path = getObj('path',details.path); highlight = getObj('graphic',details.highlight); if(details.path === id){ if(path){ path.remove(); } if(highlight){ highlight.remove(); } delete state.DryErase.drawings[details.path]; delete state.DryErase.drawings[details.highlight]; } else { if(highlight){ highlight.remove(); } delete state.DryErase.drawings[details.highlight]; if(path && 'gmlayer' === path.get('layer')){ path.remove(); delete state.DryErase.drawings[details.path]; } else { delete details.highlight; } } } }, grantPlayer = function(id){ state.DryErase.grantedPlayers = _.union( state.DryErase.grantedPlayers, [id]); sendChat('','/w gm '+getPlayerGrantOption(id)); }, revokePlayer = function(id){ state.DryErase.grantedPlayers = _.without( state.DryErase.grantedPlayers, id); sendChat('','/w gm '+getPlayerGrantOption(id)); }, handleInput = function(msg) { var args, who; if (msg.type !== "api" || !playerIsGM(msg.playerid)) { return; } who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); args = msg.content.split(/\s+--/); switch(args.shift()) { case '!dry-erase': if(0 === args.length || _.contains(args,'help')) { showHelp(who); return; } _.each(args,function(arg){ var params=arg.split(/\s+/); switch(params.shift()){ case 'erase': _.each(params,eraseDrawing); break; case 'allow': _.each(params,allowDrawing); break; case 'grant-playerid': _.each(params,grantPlayer); break; case 'revoke-playerid': _.each(params,revokePlayer); break; } }); break; case '!dry-erase-config': if(_.contains(args,'help')) { showHelp(who); return; } if(!args.length) { sendChat('','/w "'+who+'" ' +'
' +'
' +'DryErase v'+version +'
' +getAllConfigOptions() +'
' ); return; } _.each(args,function(a){ var opt=a.split(/\|/); switch(opt.shift()) { case 'toggle-auto-delete': state.DryErase.config.autoDelete=!state.DryErase.config.autoDelete; sendChat('','/w "'+who+'" ' +'
' +getConfigOption_AutoDelete() +'
' ); break; case 'toggle-label-granted': state.DryErase.config.labelGranted=!state.DryErase.config.labelGranted; sendChat('','/w "'+who+'" ' +'
' +getConfigOption_LabelGranted() +'
' ); break; case 'toggle-auto-hide': state.DryErase.config.autoHide=!state.DryErase.config.autoHide; sendChat('','/w "'+who+'" ' +'
' +getConfigOption_AutoHide() +'
' ); break; case 'toggle-report': state.DryErase.config.report=!state.DryErase.config.report; sendChat('','/w "'+who+'" ' +'
' +getConfigOption_Report() +'
' ); break; default: sendChat('','/w "'+who+'" ' +'
Unsupported Option:
'+a+'' ); } }); break; } }, allowedToDraw = function(playerid){ return playerIsGM(playerid) || _.contains(state.DryErase.grantedPlayers,playerid) || _.contains(['all',''],playerid); }, makeHighlight = function(path,player){ return createObj('graphic',{ imgsrc: clearURL, layer: 'gmlayer', pageid: path.get('pageid'), width: path.get('width'), height: path.get('height'), left: path.get('left'), top: path.get('top'), name: 'Created by: '+((player && player.get && player.get('displayname')) || 'UNKNOWN'), aura1_color: (player && player.get && player.get('color')) || '#ff0000', aura1_square: true, aura1_radius: 0.000001, showname: true }); }, handlePathDraw = function(path){ var msg ='', playerid=path.get('controlledby').split(/,/)[0], player, page, highlight, details={ path: path.id, player: playerid }; if(_.has(state.DryErase.drawings, path.id) || 'objects' !== path.get('layer')){ return; } player=getObj('player',playerid); if(!allowedToDraw(playerid)) { if(state.DryErase.config.autoDelete){ path.remove(); msg='DELETED'; } else if(state.DryErase.config.autoHide){ path.set({layer:'gmlayer'}); highlight=makeHighlight(path,player); details.highlight = highlight.id; state.DryErase.drawings[path.id]=details; state.DryErase.drawings[highlight.id]=details; msg += makeButton( '!dry-erase --erase '+path.id, 'Erase', '#faa732' ); msg += makeButton( '!dry-erase --allow '+path.id, 'Allow', '#5bb75b' ); msg += makeButton( '!dry-erase --grant-playerid '+playerid, 'Grant Player' ); } if(state.DryErase.config.report){ page=getObj('page',path.get('pageid')); sendChat('DryErase','/w gm '+ '
'+ ''+ ((player && player.get && player.get('displayname')) || 'UNKNOWN')+ ''+ ' created a drawing on the page '+ ''+ page.get('name')+ ''+ '. '+msg+ '
' ); } } else { if(state.DryErase.config.labelGranted && _.contains(state.DryErase.grantedPlayers,playerid) ){ highlight=makeHighlight(path,player); details.highlight = highlight.id; state.DryErase.drawings[highlight.id]=details; } state.DryErase.drawings[path.id]=details; } }, handleMoves = function(obj,prev){ var details, other; if(_.has(state.DryErase.drawings,obj.id)){ details=state.DryErase.drawings[obj.id]; if(obj.get('layer') !== prev.layer){ allowDrawing(obj.id); } if (_.has(details,'highlight')) { other = (details.path === obj.id ? getObj('graphic',details.highlight) : getObj('path',details.path) ); if(other){ other.set({ width: prev.width, height: prev.height, top: obj.get('top'), left: obj.get('left') }); } } } else if ('path' === obj.get('type') && 'objects' === obj.get('layer')){ handlePathDraw(obj); } }, handleDeletes = function(obj){ eraseDrawing(obj.id); }, registerEventHandlers = function() { on('chat:message', handleInput); on('add:path', handlePathDraw); on('destroy:path', handleDeletes); on('destroy:graphic', handleDeletes); on('change:path', handleMoves); on('change:graphic', handleMoves); }; return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); on('ready',function() { 'use strict'; DryErase.CheckInstall(); DryErase.RegisterEventHandlers(); });