From 224f3339c4075d19981b83c37e740457e2402c67 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:33:04 -0600 Subject: [PATCH 01/42] Squashed 'Ammo/' content from commit 45b14cc git-subtree-dir: Ammo git-subtree-split: 45b14cc669a1d05eb2490e5329cc58e48c75ece0 --- Ammo.js | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 22 ++++ 2 files changed, 337 insertions(+) create mode 100644 Ammo.js create mode 100644 package.json diff --git a/Ammo.js b/Ammo.js new file mode 100644 index 0000000000..b38b7f047f --- /dev/null +++ b/Ammo.js @@ -0,0 +1,315 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/Ammo/Ammo.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var Ammo = Ammo || (function() { + 'use strict'; + + var version = 0.2, + schemaVersion = 0.1, + + 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 ''; + }, + sendMessage = function(message, who) { + sendChat('Ammo', (who ? ('/w '+who+' ') : '') + '
' + +message + +'
' + ); + +sendChat('','
' + +'' + +'I am the title' + +'' + +'description, could be long' ++'
'); + + }, + + adjustAmmo = function (playerid,attr,amount) { + var val = parseInt(attr.get('current'),10)||0, + max = parseInt(attr.get('max'),10)||10000, + adj = (val+amount), + chr = getObj('character',attr.get('characterid')), + valid = true; + + if(adj < 0 ) { + sendMessage( + ''+chr.get('name') + ' does not have enough ammo. Needs '+Math.abs(amount)+', but only has ' + +''+val+'.' + +''+max+'.' + +'' + +'
' + +'Ammo v'+version + +'
' + +'
' + +'

Ammo provides inventory management for ammunition stored in a character' + +'attribute. If the adjustment would change the attribute to be below 0 or above' + +'it\'s maximum value, a warning will be issued and the attribute will not be' + +'changed.

' + + +( (isGM(playerid)) ? '

Note: As the GM, bounds will not be ' + +'enforced for you. You will be whispered the warnings, but the operation ' + +'will succeed. You will also be told the previous and current state in case ' + +'you want to revert the change.' : '') + + +'

' + +'Commands' + +'
' + +'!ammo '+ch('<')+'id'+ch('>')+' '+ch('<')+'attribute'+ch('>')+' '+ch('<')+'amount'+ch('>')+' ' + +'
' + +'This command requires 3 parameters:' + +'
    ' + +'
  • ' + +'id -- The id of the character which has the attribute. You can pass this as '+ch('@')+ch('{')+'selected|token_id'+ch('}')+' and the character id will be pulled from represents field of the token.' + +'
  • ' + +'
  • ' + +'attribute -- The name of the attribute representing ammunition.' + +'
  • ' + +'
  • ' + +'amount -- The change to apply to the current quantity of ammo. Use negative numbers to decrease the amount, and possitive numbers to increase it.' + +'
  • ' + +'
' + +'
' + +'
' + +'
' + +'!get-represents [token_id]' + +'
' + +'This command will show the character id for the supplied token id, or will list the character ids for all the selected tokens.' + +'
    ' + +'
  • ' + +'token_id -- The id of the token to lookup, usually supplied with: '+ch('@')+ch('{')+'selected|token_id'+ch('}') + +'
  • ' + +'
' + +'
' + +'
' ++'' + ); + }, + + HandleInput = function(msg_orig) { + var msg = _.clone(msg_orig), + args,attr,amount,chr,token,text=''; + + if (msg.type !== "api") { + return; + } + + if(_.has(msg,'inlinerolls')){ + msg.content = _.chain(msg.inlinerolls) + .reduce(function(m,v,k){ + m['$[['+k+']]']=v.results.total || 0; + return m; + },{}) + .reduce(function(m,v,k){ + return m.replace(k,v); + },msg.content) + .value(); + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!ammo': + if(args.length > 1) { + + switch(args[1]) { + case 'recover': // + // apply policy to put amounts back in + case 'check': // + // print the amount in the attribute nicely formatted + case 'recovery-policy': // [character/token_id] [attribute_name] + // create the right entry in the state + case 'name': // [character/token_id] [attribute_name] + // create the right entry in the state + + // configs + case 'recovery-updates-maximum': + // adjust the maximum to be whatever the current is + case 'create-attributes-automatically': + // create the requested attribute and start using it. + } + + + chr = getObj('character', args[1]); + if( ! chr ) { + token = getObj('graphic', args[1]); + if(token) { + chr = getObj('character', token.get('represents')); + } + } + if(chr) { + if(! isGM(msg.playerid) + && ! _.contains(chr.get('controlledby').split(','),msg.playerid) + && ! _.contains(chr.get('controlledby').split(','),'all') + ) + { + sendMessage( 'You do not control the specified character: '+chr.id ); + sendMessage( + ''+getObj('player',msg.playerid).get('_displayname')+' attempted to adjust attribute '+args[2]+' on character '+chr.get('name')+'.', + 'gm' + ); + return; + } + + + attr = findObjs({_type: 'attribute', _characterid: chr.id, name: args[2]})[0]; + } + amount=parseInt(args[3],10); + if(attr && amount) { + adjustAmmo(msg.playerid,attr,amount); + } else { + if(attr) { + sendMessage( + 'Amount ['+args[3]+'] is not correct. Please specify a positive or negative integer value like -1 or 4.', + (isGM(msg.playerid) ? 'gm' : false) + ); + + } else { + if(chr) { + sendMessage( + 'Attribute ['+args[2]+'] was not found. Please verify that you have the right name.', + (isGM(msg.playerid) ? 'gm' : false) + ); + } else { + sendMessage( + ( (undefined !== token) ? ('Token id ['+args[1]+'] does not represent a character. ') : ('Character/Token id ['+args[1]+'] is not valid. ') ) + +'Please be sure you are specifying it correctly, either with '+ch('@')+ch('{')+'selected|token_id'+ch('}')+' or copying the character id from: !get-represents '+ch('@')+ch('{')+'selected|token_id'+ch('}'), + (isGM(msg.playerid) ? 'gm' : false) + ); + } + } + } + } else { + showHelp(msg.playerid); + } + break; + + case '!get-represents': + if(args.length > 1) { + chr = getObj('character', args[1]); + if( ! chr ) { + token = getObj('graphic', args[1]); + if(token) { + sendMessage( + 'The specified token represents the following character:' + +'
  • '+ ( ('' !== token.get('name')) ? token.get('name') : 'BLANK' )+' -> '+ ( ('' !== token.get('represents')) ? token.get('represents') : 'NOTHING' ) + '
', + (isGM(msg.playerid) ? 'gm' : false) + ); + } else { + sendMessage( + ' Token id ['+args[1]+'] is not valid.', + (isGM(msg.playerid) ? 'gm' : false) + ); + } + } + } else if (msg.selected && msg.selected.length) { + _.each(msg.selected, function(s) { + token = getObj('graphic', s._id); + if(token) { + text += '
  • '+ ( ('' !== token.get('name')) ? token.get('name') : 'BLANK' )+' -> '+ ( ('' !== token.get('represents')) ? token.get('represents') : 'NOTHING' ) + '
  • '; + } + }); + sendMessage( + 'The selected tokens represent the following characters:'+'
      ' + text + '
    ', + (isGM(msg.playerid) ? 'gm' : false) + ); + } else { + showHelp(msg.playerid); + } + break; + } + + }, + checkInstall = function() { + if( ! _.has(state,'Ammo') || state.Ammo.version !== schemaVersion) { + state.Ammo = { + version: schemaVersion, + config: { + }, + policies: { + global: { + recoveryUpdatesMaximum: false + }, + byAttribute: { + }, + byCharacter: { + } + } + }; + } + }, + + RegisterEventHandlers = function() { + on('chat:message', HandleInput); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: RegisterEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + Ammo.CheckInstall(); + Ammo.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('Ammo requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..337e7c6467 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "Ammo", + "version": "0.2", + "description": "Provides inventory management for ammunition stored in an attribute of a character.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.Ammo": "read,write", + "attribute.characterid": "read", + "attribute.current": "read,write", + "attribute.max": "read", + "character.controlledby": "read", + "character.name": "read", + "graphic.represents": "read", + "player.displayname": "read" + }, + "conflicts": [ + ] +} From 737dec88cf82c904ff55f8e45263ced529e8e7b5 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:49 -0600 Subject: [PATCH 02/42] Squashed 'APIHeartBeat/' content from commit 43ec732 git-subtree-dir: APIHeartBeat git-subtree-split: 43ec7321ff15cda08233775aec674719ca180358 --- APIHeartBeat.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ pacakge.json | 14 ++++++++ 2 files changed, 110 insertions(+) create mode 100644 APIHeartBeat.js create mode 100644 pacakge.json diff --git a/APIHeartBeat.js b/APIHeartBeat.js new file mode 100644 index 0000000000..7f91e985e8 --- /dev/null +++ b/APIHeartBeat.js @@ -0,0 +1,96 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/APIHeartBeat/APIHeartBeat.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var APIHeartBeat = APIHeartBeat || (function() { + 'use strict'; + + var version = 0.1, + schemaVersion = 0.1, + beatInterval = false, + beatPeriod = 400, + beatCycle = (beatPeriod * 8), + + animateHeartBeat = function() { + var x = (Date.now()%beatCycle)*12.5, + scale = Math.max(0, ( Math.sin( (x-1)/2) - Math.sin(x-(1+(Math.PI/2))))), + beatColor = Math.round(0xff*scale).toString(16)+'0000'; + + _.chain(state.APIHeartBeat.heartBeaters) + .map(function(pid){ + return getObj('player',pid); + }) + .filter(_.isUndefined) + .each(function(p){ + p.set({ + color: beatColor + }); + }); + }, + + startStopBeat = function() { + if(!beatInterval && state.APIHeartBeat.heartBeaters.length) { + beatInterval = setInterval(animateHeartBeat,beatPeriod); + } else if(beatInterval && !state.APIHeartBeat.heartBeaters.length) { + clearInterval(beatInterval); + } + }, + + handleInput = function(msg) { + var args; + + if (msg.type !== "api") { + return; + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!api-heartbeat': + if(_.contains(state.APIHeartBeat.heartBeaters, msg.playerid)) { + state.APIHeartBeat.heartBeaters=_.without(state.APIHeartBeat.heartBeaters, msg.playerid); + } else { + state.APIHeartBeat.heartBeaters.push(msg.playerid); + } + + startStopBeat(); + + break; + } + }, + + checkInstall = function() { + if( ! _.has(state,'APIHeartBeat') || state.APIHeartBeat.version !== schemaVersion) { + log('APIHeartBeat: Resetting state'); + state.APIHeartBeat = { + version: schemaVersion, + heartBeaters: [] + }; + } + + startStopBeat(); + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function() { + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + APIHeartBeat.CheckInstall(); + APIHeartBeat.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('APIHeartBeat requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/pacakge.json b/pacakge.json new file mode 100644 index 0000000000..209e7e4fb7 --- /dev/null +++ b/pacakge.json @@ -0,0 +1,14 @@ +{ + "name": "APIHeartBeat", + "version": "0.1", + "description": "Provides an API Heartbeat by setting the requesting player's color continuously.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "state.APIHeartBeat": "read,write", + }, + "conflicts": [ + ] +} From 22524ba1f00529f82193b3a5fb100d97a84305cf Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:50 -0600 Subject: [PATCH 03/42] Squashed 'AnnounceRoll/' content from commit 227aa57 git-subtree-dir: AnnounceRoll git-subtree-split: 227aa5732c98524c0a9190694bb2dc6b916e7a42 --- AnnounceRoll.js | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 ++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 AnnounceRoll.js create mode 100644 package.json diff --git a/AnnounceRoll.js b/AnnounceRoll.js new file mode 100644 index 0000000000..fcbdd84b22 --- /dev/null +++ b/AnnounceRoll.js @@ -0,0 +1,53 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/AnnouncRoll/AnnouncRoll.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var AnnounceRoll = AnnounceRoll || (function() { + 'use strict'; + + var version = 0.1, + + handleInput = function(msg) { + var rolldata,out=[]; + + if (msg.type !== "rollresult") { + return; + } + + rolldata = JSON.parse(msg.content); + _.each(rolldata.rolls,function(r){ + if('R' === r.type && 20 === r.sides) { + _.each(r.results, function(roll){ + switch(roll.v) { + case 1: + out.push('
    Fumble!
    '); + break; + case 20: + out.push('
    Critical!
    '); + break; + } + }); + } + }); + if(out.length) { + sendChat('',out.join('')); + } + + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function() { + 'use strict'; + + AnnounceRoll.RegisterEventHandlers(); +}); + diff --git a/package.json b/package.json new file mode 100644 index 0000000000..85a3c60a30 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "AnnounceRoll", + "version": "0.1", + "description": "Prints an extra announcment of a critical or fumble to the chat when using /roll", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + }, + "conflicts": [ + ] +} From 1f81fbbc74ab954e40617bffcece84f9c795e56c Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:51 -0600 Subject: [PATCH 04/42] Squashed 'Base64/' content from commit 4d1d93e git-subtree-dir: Base64 git-subtree-split: 4d1d93eee469ec4a3aa68625a5a951be1622b013 --- Base64.js | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 +++++ 2 files changed, 162 insertions(+) create mode 100644 Base64.js create mode 100644 package.json diff --git a/Base64.js b/Base64.js new file mode 100644 index 0000000000..21b05a0a7a --- /dev/null +++ b/Base64.js @@ -0,0 +1,149 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/Base64/Base64.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron +// modified from: http://www.webtoolkit.info/ + +var Base64 = Base64 || (function () { + 'use strict'; + + var version = 0.2, + keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + + // private method for UTF-8 encoding + utf8_encode = function (string) { + var utftext = '', + n, c1; + + for (n = 0; n < string.length; n++) { + + c1 = string.charCodeAt(n); + + if (c1 < 128) { + utftext += String.fromCharCode(c1); + } + else if((c1 > 127) && (c1 < 2048)) { + utftext += String.fromCharCode((c1 >> 6) | 192); + utftext += String.fromCharCode((c1 & 63) | 128); + } + else { + utftext += String.fromCharCode((c1 >> 12) | 224); + utftext += String.fromCharCode(((c1 >> 6) & 63) | 128); + utftext += String.fromCharCode((c1 & 63) | 128); + } + + } + + return utftext; + }, + + // private method for UTF-8 decoding + utf8_decode = function (utftext) { + var string = '', + i = 0, + c1 = 0, + c2 = 0, + c3 = 0; + + while ( i < utftext.length ) { + + c1 = utftext.charCodeAt(i); + + if (c1 < 128) { + string += String.fromCharCode(c1); + i++; + } + else if((c1 > 191) && (c1 < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + + } + + return string; + }, + + encode = function (input) { + var output = '', + chr1, chr2, chr3, enc1, enc2, enc3, enc4, + i = 0; + + input = utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + + } + + return output; + }, + + // public method for decoding + decode = function (input) { + var output = '', + chr1, chr2, chr3, + enc1, enc2, enc3, enc4, + i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 !== 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 !== 64) { + output = output + String.fromCharCode(chr3); + } + + } + + output = utf8_decode(output); + + return output; + + } + + ; + + return { + encode: encode, + decode: decode + }; + +}()); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..c997f03205 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "Base64", + "version": "0.2", + "description": "Base 64 encoding for Roll20 Scripts. (Adapted from http://www.webtoolkit.info/ )", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + }, + "conflicts": [ + ] +} From d54309daa086e1855535a03f2c4517c10bdb3694 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:52 -0600 Subject: [PATCH 05/42] Squashed 'BashDice/' content from commit 24e9e62 git-subtree-dir: BashDice git-subtree-split: 24e9e624f33cc9be4d486952c9ca7576e881528e --- BashDice.js | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 ++++++ 2 files changed, 130 insertions(+) create mode 100644 BashDice.js create mode 100644 package.json diff --git a/BashDice.js b/BashDice.js new file mode 100644 index 0000000000..9105af9997 --- /dev/null +++ b/BashDice.js @@ -0,0 +1,117 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/BashDice/BashDice.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + 'use strict'; + + var version = 0.1, + + 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 ''; + }, + + getBashRoll = function() { + var rolls = [randomInteger(6),randomInteger(6)]; + while( 1 === _.uniq(rolls).length ) { + rolls.push(randomInteger(6)); + } + return rolls; + }, + + getFormatForDice = function (dice, multiplier,result) { + var maxroll = 0, + minroll = 0, + dicePart = _.chain(dice) + .map(function(r){ + maxroll += ( 6 === r ? 1 : 0 ); + minroll += ( 1 === r ? 1 : 0 ); + return { + value: r, + style: ( 6 === r ? 'critsuccess' : ( 1 === r ? 'critfail' : 'basicdiceroll')) + }; + }) + .map(function(o){ + return ''+o.value+''; + }) + .value().join('+'), + rollOut = '' + result + ''; + return rollOut; + }, + + handleInput = function(msg_orig) { + var msg = _.clone(msg_orig), + args, multiplier, dice, result; + + if (msg.type !== "api") { + return; + } + + if(_.has(msg,'inlinerolls')){ + msg.content = _.chain(msg.inlinerolls) + .reduce(function(m,v,k){ + m['$[['+k+']]']=v.results.total || 0; + return m; + },{}) + .reduce(function(m,v,k){ + return m.replace(k,v); + },msg.content) + .value(); + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!bash': + case '!bd': + multiplier = parseInt(args[1],10) || 1; + dice = getBashRoll(); + result = _.reduce(dice,function(m,d){ return m+d; }, 0) * multiplier; + sendChat('','Bash Roll: '+getFormatForDice(dice,multiplier,result)); + break; + + case '!gbash': + case '!gbd': + multiplier = parseInt(args[1],10) || 1; + dice = getBashRoll(); + result = _.reduce(dice,function(m,d){ return m+d; }, 0) * multiplier; + sendChat('','/w gm Bash Roll: '+getFormatForDice(dice,multiplier,result)); + break; + } + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function() { + 'use strict'; + + BashDice.RegisterEventHandlers(); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..8cefa03399 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "BashDice", + "version": "0.1", + "description": "Rolls the Bash UE Dice format with exploding.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + }, + "conflicts": [ + ] +} From 3bea405280b5dba27a1bcbc232235d75735466cd Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:52 -0600 Subject: [PATCH 06/42] Squashed 'DarknessClosingIn/' content from commit d64dd24 git-subtree-dir: DarknessClosingIn git-subtree-split: d64dd24f3ef3dcd337dec51bed6d4a42c97feb75 --- DarknessClosingIn.js | 75 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 18 +++++++++++ 2 files changed, 93 insertions(+) create mode 100644 DarknessClosingIn.js create mode 100644 package.json diff --git a/DarknessClosingIn.js b/DarknessClosingIn.js new file mode 100644 index 0000000000..dbd596b8a0 --- /dev/null +++ b/DarknessClosingIn.js @@ -0,0 +1,75 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/DarknessClosingIn/DarknessClosingIn.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var DarknessClosingIn = DarknessClosingIn || (function() { + 'use strict'; + + var version = 0.1, + schemaVersion = 0.1, + + checkInstall = function() { + if( ! _.has(state,'DarknessClosingIn') || state.DarknessClosingIn.version !== schemaVersion) { + log('DarknessClosingIn: Resetting state'); + state.DarknessClosingIn = { + version: schemaVersion, + gettingDarker: false + }; + } + }, + + handleInput = function(msg) { + var args; + + if (msg.type !== "api" || ! isGM(msg.playerid) ) { + return; + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!DarknessClosingIn': + state.DarknessClosingIn.gettingDarker = ! state.DarknessClosingIn.gettingDarker; + sendChat('','/w gm ' + ( state.DarknessClosingIn.gettingDarker ? 'Darkness is now closing in.' : 'Darkness is no longer closing in.' ) ); + break; + } + }, + + gettingDarker = function(obj, prev) { + if( state.DarknessClosingIn.gettingDarker + && ( + obj.get("left") !== prev.left + || obj.get("top") !== prev.top + ) + ) { + obj.set({ + light_radius: Math.floor(obj.get("light_radius") * 0.90) + }); + } + }, + + + registerEventHandlers = function() { + on('chat:message', handleInput); + on("change:token", gettingDarker); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function() { + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + DarknessClosingIn.CheckInstall(); + DarknessClosingIn.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('DarknessClosingIn requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..af74d56e14 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "DarknessClosingIn", + "version": "0.1", + "description": "A command wrapped version of the Darkness Closing In API sample script.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.DarknessClosingIn": "read,write", + "graphic.left": "read", + "graphic.light_radius": "read,write", + "graphic.top": "read" + }, + "conflicts": [ + ] +} From fc4f3801be34a3121dc2bf44eb60e765b5ed8b67 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:53 -0600 Subject: [PATCH 07/42] Squashed 'Escalation/' content from commit 2165e79 git-subtree-dir: Escalation git-subtree-split: 2165e796e4fa474576fdbadc1d291328a1e7406e --- Escalation.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 +++++++++++ 2 files changed, 90 insertions(+) create mode 100644 Escalation.js create mode 100644 package.json diff --git a/Escalation.js b/Escalation.js new file mode 100644 index 0000000000..9d7e335e3b --- /dev/null +++ b/Escalation.js @@ -0,0 +1,75 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/Escalation/Escalation.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var Escalation = Escalation || (function() { + 'use strict'; + + var version = 0.1, + dieName = 'Escalation Die', + dieMax = 6, + + handleEscalationDieReset = function(obj, prev) { + var to=JSON.parse(obj.get('turnorder')), + loc=_.find(to,function(e){ + return dieName === e.custom; + }); + + if(loc) { + if(!obj.get('initiativepage')) { + loc.pr=0; + loc.forumla='+1'; + } else if (dieMax !== loc.pr && '' === loc.formula) { + loc.formula='+1'; + } + } else { + to = _.union([{ + id: '-1', + pr: 0, + custom: dieName, + formula: '+1'}], to); + } + obj.set({turnorder: JSON.stringify(to)}); + }, + + reset = function() { + if( "" === Campaign().get('turnorder')) { + Campaign().set({turnorder: '[]'}); + } + handleEscalationDieReset(Campaign(),{}); + }, + + handleEscalationDieTurn = function(obj, prev) { + var to,lastto; + handleEscalationDieReset(obj,prev); + + to=JSON.parse(obj.get('turnorder')) || []; + lastto=JSON.parse(prev.turnorder) || []; + + if((to[0] !== lastto[0]) + && _.has(to[0],'custom') + && dieName === to[0].custom + && dieMax <= to[0].pr) { + to[0].formula=''; + to[0].pr=dieMax; + } + obj.set({turnorder: JSON.stringify(to)}); + }, + + registerEventHandlers = function() { + on("change:campaign:initiativepage", handleEscalationDieReset ); + on("change:campaign:turnorder", handleEscalationDieTurn ); + }; + + return { + RegisterEventHandlers: registerEventHandlers, + Reset: reset + }; +}()); + +on("ready",function(){ + 'use strict'; + + Escalation.Reset(); + Escalation.RegisterEventHandlers(); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..6a1ee6db12 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "Escalation", + "version": "0.1", + "description": "Simple Escalation Die handling script, using the new custom entries with formulae.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "campaign.initiativepage": "read", + "campaign.turnorder": "read,write" + }, + "conflicts": [ + ] +} From 95fd8ac24460db2401f2bf5ea648391f4a8add06 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:54 -0600 Subject: [PATCH 08/42] Squashed 'FateDots/' content from commit 2ec1e5f git-subtree-dir: FateDots git-subtree-split: 2ec1e5f4be02917bfc2b1b189041111d2a5233c6 --- FateDots.js | 225 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 ++++ 2 files changed, 241 insertions(+) create mode 100644 FateDots.js create mode 100644 package.json diff --git a/FateDots.js b/FateDots.js new file mode 100644 index 0000000000..060729cd7f --- /dev/null +++ b/FateDots.js @@ -0,0 +1,225 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/FateDots/FateDots.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var FateDots = FateDots || (function(){ + 'use strict'; + + var version = 0.1, + schemaVersion = 0.3, + regex = { + statuses: /^(?:red|blue|green|brown|purple|pink|yellow|skull|sleepy|half-heart|half-haze|interdiction|snail|lightning-helix|spanner|chained-heart|chemical-bolt|death-zone|drink-me|edge-crack|ninja-mask|stopwatch|fishing-net|overdrive|strong|fist|padlock|three-leaves|fluffy-wing|pummeled|tread|arrowed|aura|back-pain|black-flag|bleeding-eye|bolt-shield|broken-heart|cobweb|broken-shield|flying-flag|radioactive|trophy|broken-skull|frozen-orb|rolling-bomb|white-tower|grab|screaming|grenade|sentry-gun|all-for-one|angel-outfit|archery-target)$/ + }, + + 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 ' ++'
    ' + +'
    ' + +'FateDots v'+version + +'
    ' + +'
    ' + +'
    ' + +'

    Allows statues to be used like Fate stress boxes by repeating them.

    ' + +'

    By default, Blue and Red will be treated specially. Assigning a number to them will cause them to be duplicated that many times, and numbered in decreasing order. Changing the number will change the number of pips that appear.

    ' + +'
    ' + +'Commands' + +'
    !fate-dots '+ch('<')+ch('[')+'+'+ch('|')+'-'+ch(']')+'Status Marker'+ch('>')+'' + +'
    ' + +'Adds or removes a status to be treated as a multibox.' + +'
    ' + +'

    Available Status Markers:

    ' + +'
    red
    ' + +'
    blue
    ' + +'
    green
    ' + +'
    brown
    ' + +'
    purple
    ' + +'
    pink
    ' + +'
    yellow
    ' + +'
    skull
    ' + +'
    sleepy
    ' + +'
    half-heart
    ' + +'
    half-haze
    ' + +'
    interdiction
    ' + +'
    snail
    ' + +'
    lightning-helix
    ' + +'
    spanner
    ' + +'
    chained-heart
    ' + +'
    chemical-bolt
    ' + +'
    death-zone
    ' + +'
    drink-me
    ' + +'
    edge-crack
    ' + +'
    ninja-mask
    ' + +'
    stopwatch
    ' + +'
    fishing-net
    ' + +'
    overdrive
    ' + +'
    strong
    ' + +'
    fist
    ' + +'
    padlock
    ' + +'
    three-leaves
    ' + +'
    fluffy-wing
    ' + +'
    pummeled
    ' + +'
    tread
    ' + +'
    arrowed
    ' + +'
    aura
    ' + +'
    back-pain
    ' + +'
    black-flag
    ' + +'
    bleeding-eye
    ' + +'
    bolt-shield
    ' + +'
    broken-heart
    ' + +'
    cobweb
    ' + +'
    broken-shield
    ' + +'
    flying-flag
    ' + +'
    radioactive
    ' + +'
    trophy
    ' + +'
    broken-skull
    ' + +'
    frozen-orb
    ' + +'
    rolling-bomb
    ' + +'
    white-tower
    ' + +'
    grab
    ' + +'
    screaming
    ' + +'
    grenade
    ' + +'
    sentry-gun
    ' + +'
    all-for-one
    ' + +'
    angel-outfit
    ' + +'
    archery-target
    ' + + +'
    '+ch(' ')+'
    ' + +'

    Adding purple and skull, removing blue.

    ' + +'
    ' + +'
    '
    +					+'!fate-dots +purple +skull -blue'
    +				+'
    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + + handleInput = function(msg) { + var args; + + if ( "api" !== msg.type || !isGM(msg.playerid) ) { + return; + } + + + args = msg.content.split(/\s+/); + + switch(args.pop()) { + case '!fate-dots': + if( !args.length ) { + showHelp(); + return; + } + + _.each(args,function(s){ + var op = s[0], + st = s.substring(1); + if(st.match(regex.statuses)) { + switch(op) { + case '+': + state.FateDots.statuses=_.union( state.FateDots.statuses,[st] ); + break; + + case '-': + state.FateDots.statuses=_.without( state.FateDots.statuses, st ); + break; + + } + } + }); + + break; + } + + }, + statusmarkersToObject = function(stats) { + return _.reduce(stats.split(/,/),function(memo,st){ + var parts=st.split(/@/), + num=parseInt(parts[1]||'0',10); + if(parts[0].length) { + memo[parts[0]]=Math.max(num,memo[parts[0]]||0); + } + return memo; + },{}); + }, + + statusChangeHandler = function(obj) { + + var nstat = statusmarkersToObject(obj.get('statusmarkers')), + rstat = _.reduce(nstat,function(m,c,s){ + if(_.contains(state.FateDots.statuses,s)) { + m.push(_.reduce(_.range(1,c+1),function(m2,n){ + m2.push(s+'@'+n); + return m2; + },[]).join(',')); + } else { + m.push('dead' === s ? s : s+'@'+c); + } + return m; + },[]).join(','); + obj.set({ + statusmarkers: rstat + }); + }, + + + checkInstall = function() { + if( ! _.has(state,'FateDots') || state.FateDots.version !== schemaVersion) { + log('FateDots: Resetting state'); + + state.FateDots = { + version: schemaVersion, + statuses: ['blue','red'] + }; + } + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + on('change:graphic:statusmarkers',statusChangeHandler); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + FateDots.CheckInstall(); + FateDots.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('FateDots requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..d41a2adcbb --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "FateDots", + "version": "0.1", + "description": "Provides numbered multi dots for Fate stress boxes.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.FateDots": "read,write", + "graphic.statusmarkers": "read,write" + }, + "conflicts": [ + ] +} From 0a856ce12fd1bd4b92ef7307275e5484802ded05 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:54 -0600 Subject: [PATCH 09/42] Squashed 'GroupInitiative/' content from commit 2ef0687 git-subtree-dir: GroupInitiative git-subtree-split: 2ef0687573a149d38eab123281e5e3017b6b2f65 --- GroupInitiative.js | 572 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 17 ++ 2 files changed, 589 insertions(+) create mode 100644 GroupInitiative.js create mode 100644 package.json diff --git a/GroupInitiative.js b/GroupInitiative.js new file mode 100644 index 0000000000..d7ed908094 --- /dev/null +++ b/GroupInitiative.js @@ -0,0 +1,572 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/GroupInitiative/GroupInitiative.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var GroupInitiative = GroupInitiative || (function() { + 'use strict'; + + var version = 0.41, + schemaVersion = 0.5, + bonusCache = {}, + statAdjustments = { + 'Stat-DnD': { + func: function(v) { + return Math.floor((v-10)/2); + }, + desc: 'Calculates the bonus as if the value were a DnD Stat.' + }, + 'Bare': { + func: function(v) { + return v; + }, + desc: 'No Adjustment.' + }, + 'Floor': { + func: function(v) { + return Math.floor(v); + }, + desc: 'Rounds down to the nearest integer.' + }, + 'Ceiling': { + func: function(v) { + return Math.ceil(v); + }, + desc: 'Rounds up to the nearest integer.' + }, + 'Bounded': { + func: function(v,l,h) { + l=parseFloat(l,10) || v; + h=parseFloat(h) || v; + return Math.min(h,Math.max(l,v)); + }, + desc: 'Restricts to a range. Use Bounded:: for specifying bounds. Leave a bound empty to be unrestricted in that direction. Example: Bounded::5 would specify a maximum of 5 with no minimum.' + } + }, + + rollers = { + 'Least-All-Roll':{ + func: function(s,k,l){ + if(!_.has(this,'init')) { + this.init=_.chain(l) + .pluck('bonus') + .map(function(d){ + return randomInteger(20)+d; + },{}) + .min() + .value(); + } + s.init=this.init; + return s; + }, + desc: 'Sets the initiative to the lowest of all initiatives rolled for the group.' + }, + 'Mean-All-Roll':{ + func: function(s,k,l){ + if(!_.has(this,'init')) { + this.init=_.chain(l) + .pluck('bonus') + .map(function(d){ + return randomInteger(20)+d; + },{}) + .reduce(function(r,memo){ + return memo+r; + },0) + .map(function(v){ + return Math.floor(v/l.length); + }) + .value(); + } + s.init=this.init; + return s; + }, + desc: 'Sets the initiative to the mean (average) of all initiatives rolled for the group.' + }, + 'Individual-Roll': { + func: function(s,k,l){ + s.init=randomInteger(20)+s.bonus; + return s; + }, + desc: 'Sets the initiative individually for each member of the group.' + }, + 'Constant-By-Stat': { + func: function(s,k,l){ + s.init=s.bonus; + return s; + }, + desc: 'Sets the initiative individually for each member of the group to their bonus with no roll.' + } + }, + checkInstall = function() { + if( ! _.has(state,'GroupInitiative') || state.GroupInitiative.version !== schemaVersion) { + state.GroupInitiative = { + version: schemaVersion, + bonusStatGroups: [ + [ + { + attribute: 'dexterity' + } + ] + ], + rollType: 'Individual-Roll' + }; + } + }, + 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 ''; + }, + + + buildBonusStatGroupRows = function() { + return _.reduce(state.GroupInitiative.bonusStatGroups, function(memo,bsg){ + return memo + '
  • '+_.chain(bsg) + .map(function(s){ + var attr=s.attribute+'|'+( _.has(s,'type') ? s.type : 'current' ); + if(_.has(s,'adjustments')) { + attr=_.reduce(s.adjustments, function(memo2,a) { + return a+'( '+memo2+' )'; + }, attr); + } + return attr; + }) + .value() + .join(' + ') + +'
  • '; + },""); + }, + buildStatAdjustmentRows = function() { + return _.reduce(statAdjustments,function(memo,r,n){ + return memo+"
  • "+n+" — "+r.desc+"
  • "; + },""); + }, + + showHelp = function() { + var rollerRows=_.reduce(rollers,function(memo,r,n){ + var selected=((state.GroupInitiative.rollType === n) ? + '
    Selected
    ' + : '' ), + selectedStyleExtra=((state.GroupInitiative.rollType === n) ? ' style="border: 1px solid #aeaeae;background-color:#8bd87a;"' : ''); + + return memo+selected+"
  • "+n+" - "+r.desc+"
  • "; + },""), + statAdjustmentRows = buildStatAdjustmentRows(), + bonusStatGroupRows = buildBonusStatGroupRows(); + + sendChat('', + '/w gm ' + +'
    ' + +'
    ' + +'GroupInitiative v'+version + +'
    ' + +'
    ' + +'

    Rolls initiative for the selected tokens and adds them ' + +'to the turn order if they don'+ch("'")+'t have a turn yet.

    ' + + +'

    The calculation of initiative is handled by the ' + +'combination of Roller (See Roller Options below) and ' + +'a Bonus. The Bonus is determined based on an ordered list ' + +'of Stat Groups (See Bonus Stat Groups below). Stat ' + +'Groups are evaluated in order. The bonus computed by the first ' + +'Stat Group for which all attributes exist and have a ' + +'numeric value is used. This allows you to have several ' + +'Stat Groups that apply to different types of characters. ' + +'In practice you will probably only have one, but more are ' + +'there if you need them.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!group-init' + +'
    ' + +'

    This command uses the configured Roller to ' + +'determine the initiative order for all selected ' + +'tokens.

    ' + +'
    ' + +'
    ' + + +'
    ' + +'!group-init --help' + +'
    ' + +'

    This command displays the help.

    ' + +'
    ' + +'
    ' + + +'
    ' + +'!group-init --set-roller '+ch('<')+'roller name'+ch('>')+'' + +'
    ' + +'

    Sets Roller to use for calculating initiative.

    ' + +'This command requires 1 parameter:' + +'
      ' + +'
    • ' + +'roller name -- The name of the Roller to use. See Roller Options below.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'!group-init --promote '+ch('<')+'index'+ch('>')+'' + +'
    ' + +'

    Increases the importance the specified Bonus Stat Group.

    ' + +'This command requires 1 parameter:' + +'
      ' + +'
    • ' + +'index -- The numeric index of the Bonus Stat Group to promote. See Bonus Stat Groups below.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'!group-init --del-group '+ch('<')+'index'+ch('>')+'' + +'
    ' + +'

    Deletes the specified Bonus Stat Group.

    ' + +'This command requires 1 parameter:' + +'
      ' + +'
    • ' + +'index -- The numeric index of the Bonus Stat Group to delete. See Bonus Stat Groups below.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' ++'
    ' + +'!group-init --add-group --'+ch('<')+'adjustment'+ch('>')+' [--'+ch('<')+'adjustment'+ch('>')+'] '+ch('<')+'attribute name[|'+ch('<')+'max|current'+ch('>')+']'+ch('>')+' [--'+ch('<')+'adjustment'+ch('>')+' [--'+ch('<')+'adjustment'+ch('>')+'] '+ch('<')+'attribute name[|'+ch('<')+'max|current'+ch('>')+']'+ch('>')+' ...] ' + +'
    ' + +'

    Adds a new Bonus Stat Group to the end of the list. Each adjustment operation can be followed by another adjustment operation, but eventually must end in an attriute name. Adjustment operations are applied to the result of the adjustment operations that follow them.

    ' + +'

    For example: --Bounded:-2:2 --Stat-DnD wisdom|max would first computer the DnD Stat bonus for the max field of the wisdom attribute, then bound it between -2 and +2.

    ' + +'This command takes multiple parameters:' + +'
      ' + +'
    • ' + +'adjustment -- One of the Stat Adjustment Options. See Stat Adjustment Options below.' + +'
    • ' + +'
    • ' + +'attribute name -- The name of an attribute. You can specify |max or |current on the end to target those specific fields (defaults to |current).' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + + +'Roller Options' + +'
    ' + +'
      ' + +rollerRows + +'
    ' + +'
    ' + + +'Stat Adjustment Options' + +'
    ' + +'
      ' + +statAdjustmentRows + +'
    ' + +'
    ' + + +'Bonus Stat Groups' + +'
    ' + +'
      ' + +bonusStatGroupRows + +'
    ' + +'
    ' + + +'
    ' + ); + }, + + findInitiativeBonus = function(id) { + var bonus = 0; + if(_.has(bonusCache,id)) { + return bonusCache[id]; + } + _.chain(state.GroupInitiative.bonusStatGroups) + .find(function(group){ + bonus = _.chain(group) + .map(function(details){ + var stat=parseFloat(getAttrByName(id,details.attribute, details.type||'current'),10); + + stat = _.reduce(details.adjustments || [],function(memo,a){ + var args,adjustment,func; + if(memo) { + args=a.split(':'); + adjustment=args.shift(); + args.unshift(memo); + func=statAdjustments[adjustment].func; + if(_.isFunction(func)) { + memo =func.apply({},args); + } + } + return memo; + },stat); + return stat; + }) + .reduce(function(memo,v){ + return memo+v; + },0) + .value(); + return !(_.isUndefined(bonus) && _.isNaN(bonus)); + }); + bonusCache[id]=bonus; + return bonus; + }, + + HandleInput = function(msg) { + var args, + cmds, + workgroup, + workvar, + turnorder, + error=false, + initFunc; + + if (msg.type !== "api" || !isGM(msg.playerid) ) { + return; + } + + args = msg.content.split(/\s+--/); + switch(args.shift()) { + case '!group-init': + if(args.length > 0) { + cmds=args.shift().split(/\s+/); + toString(args); + toString(cmds); + + switch(cmds[0]) { + case 'help': + showHelp(); + break; + + case 'add-group': + workgroup=[]; + workvar={}; + + _.each(args,function(arg){ + var a=arg.split(/\s+(.+)/), + b, + c=a[0].split(/:/); + + if(_.has(statAdjustments,c[0])) { + if('Bare' !== c[0]) { + if(!_.has(workvar,'adjustments')) { + workvar.adjustments=[]; + } + workvar.adjustments.unshift(a[0]); + } + if(a.length > 1){ + b=a[1].split(/\|/); + workvar.attribute=b[0]; + if('max'===b[1]) { + workvar.type = 'max'; + } + workgroup.push(workvar); + workvar={}; + } + } else { + sendChat('!group-init --add-group', '/w gm ' + +'
    ' + +'Unknown Stat Adustment: '+c[0]+'
    ' + +'Use one of the following:' + +'
      ' + +buildStatAdjustmentRows() + +'
    ' + +'
    ' + ); + error=true; + } + }); + if(!error) { + if(!_.has(workvar,'adjustments')){ + state.GroupInitiative.bonusStatGroups.push(workgroup); + sendChat('GroupInitiative', '/w gm ' + +'
    ' + +'Updated Bonus Stat Group Ordering:' + +'
      ' + +buildBonusStatGroupRows() + +'
    ' + +'
    ' + ); + } else { + sendChat('!group-init --add-group', '/w gm ' + +'
    ' + +'All Stat Adjustments must have a final attribute name as an argument. Please add an attribute name after --'+args.pop() + +'
    ' + ); + } + } + break; + + + case 'promote': + cmds[1]=Math.max(parseInt(cmds[1],10),1); + if(state.GroupInitiative.bonusStatGroups.length >= cmds[1]) { + if(1 !== cmds[1]) { + workvar=state.GroupInitiative.bonusStatGroups[cmds[1]-1]; + state.GroupInitiative.bonusStatGroups[cmds[1]-1] = state.GroupInitiative.bonusStatGroups[cmds[1]-2]; + state.GroupInitiative.bonusStatGroups[cmds[1]-2] = workvar; + } + + sendChat('GroupInitiative', '/w gm ' + +'
    ' + +'Updated Bonus Stat Group Ordering:' + +'
      ' + +buildBonusStatGroupRows() + +'
    ' + +'
    ' + ); + } else { + sendChat('!group-init --promote', '/w gm ' + +'
    ' + +'Please specify one of the following by number:' + +'
      ' + +buildBonusStatGroupRows() + +'
    ' + +'
    ' + ); + } + break; + + case 'del-group': + cmds[1]=Math.max(parseInt(cmds[1],10),1); + if(state.GroupInitiative.bonusStatGroups.length >= cmds[1]) { + state.GroupInitiative.bonusStatGroups=_.filter(state.GroupInitiative.bonusStatGroups, function(v,k){ + return (k !== (cmds[1]-1)); + }); + + sendChat('GroupInitiative', '/w gm ' + +'
    ' + +'Updated Bonus Stat Group Ordering:' + +'
      ' + +buildBonusStatGroupRows() + +'
    ' + +'
    ' + ); + } else { + sendChat('!group-init --del-group', '/w gm ' + +'
    ' + +'Please specify one of the following by number:' + +'
      ' + +buildBonusStatGroupRows() + +'
    ' + +'
    ' + ); + } + break; + + case 'set-roller': + if(_.has(rollers,cmds[1])) { + state.GroupInitiative.rollType=cmds[1]; + sendChat('GroupInitiative', '/w gm ' + +'
    ' + +'Roller is now set to: '+cmds[1]+'
    ' + +'
    ' + ); + } else { + sendChat('GroupInitiative', '/w gm ' + +'
    ' + +'Not a valid Roller Name: '+cmds[1]+'
    ' + +'Please use one of the following:' + +'
      ' + +_.reduce(rollers,function(memo,r,n){ + return memo+'
    • '+n+'
    • '; + },'') + +'
    ' + +'
    ' + ); + } + break; + default: + sendChat('GroupInitiative', '/w gm ' + +'
    ' + +'Not a valid command: '+cmds[0]+'' + +'
    ' + ); + break; + } + } else { + if(_.has(msg,'selected')) { + bonusCache = {}; + turnorder = Campaign().get('turnorder'); + turnorder = ('' === turnorder) ? [] : JSON.parse(turnorder); + + initFunc=rollers[state.GroupInitiative.rollType].func; + + Campaign().set({ + turnorder: JSON.stringify( + turnorder.concat( + _.chain(msg.selected) + .map(function(s){ + return getObj(s._type,s._id); + }) + .reject(_.isUndefined) + .reject(function(s){ + return _.contains(_.pluck(turnorder,'id'),s.id); + }) + .map(function(s){ + return { + token: s, + character: getObj('character',s.get('represents')) + }; + }) + .map(function(s){ + s.bonus=s.character ? findInitiativeBonus(s.character.id) || 0 : 0; + return s; + }) + .map(initFunc,{}) + .map(function(s){ + return { + id: s.token.id, + pr: s.init, + custom: '' + }; + }) + .value() + ) + ) + } + ); + } else { + showHelp(); + } + } + break; + } + + }, + + + RegisterEventHandlers = function() { + on('chat:message', HandleInput); + }; + + return { + RegisterEventHandlers: RegisterEventHandlers, + CheckInstall: checkInstall + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + GroupInitiative.CheckInstall(); + GroupInitiative.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('GroupInitiative requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..29c51e2eb3 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "GroupInitiative", + "version": "0.41", + "description": "Adds the selected tokens to the turn order after rolling their initiative + configurable data.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.GroupInitiative": "read,write", + "campaign.turnorder": "read,write", + "graphic.represents": "read" + }, + "conflicts": [ + ] +} From e49db58659e22bd37ff14a0b11ccba07a154676c Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:55 -0600 Subject: [PATCH 10/42] Squashed 'IsGM/' content from commit 45dfc28 git-subtree-dir: IsGM git-subtree-split: 45dfc28139f9c4f2a601092c679335b78de53b3d --- IsGMModule.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 +++++++++ 2 files changed, 112 insertions(+) create mode 100644 IsGMModule.js create mode 100644 package.json diff --git a/IsGMModule.js b/IsGMModule.js new file mode 100644 index 0000000000..cdc439ba75 --- /dev/null +++ b/IsGMModule.js @@ -0,0 +1,96 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/IsGM/IsGMModule.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var IsGMModule = IsGMModule || { + version: 0.6, + active: true, + reset_password: "swordfish", + + CheckInstall: function() { + var players = findObjs({_type:"player"}); + + if( ! _.has(state,'IsGM') || ! _.has(state.IsGM,'version') || state.IsGM.version != IsGMModule.version ) + { + state.IsGM={ + version: IsGMModule.version, + gms: [], + players: [], + unknown: [] + }; + } + state.IsGM.unknown=_.difference( + _.pluck(players,'id'), + state.IsGM.gms, + state.IsGM.players + ); + IsGMModule.active = (state.IsGM.unknown.length>0); + }, + IsGM: function(id) { + return _.contains(state.IsGM.gms,id); + }, + HandleMessages: function(msg) + { + if(msg.type != "api") + { + if(IsGMModule.active && msg.playerid != 'API') + { + if(_.contains(state.IsGM.unknown, msg.playerid)) + { + var player=getObj('player',msg.playerid); + if("" == player.get('speakingas') || 'player|'+msg.playerid == player.get('speakingas')) + { + if(msg.who == player.get('_displayname')) + { + state.IsGM.players.push(msg.playerid); + } + else + { + state.IsGM.gms.push(msg.playerid); + sendChat('IsGM','/w gm '+player.get('_displayname')+' is now flagged as a GM.') + } + state.IsGM.unknown=_.without(state.IsGM.unknown,msg.playerid); + IsGMModule.active = (state.IsGM.unknown.length>0); + } + } + } + } + else + { + var tokenized = msg.content.split(" "); + var command = tokenized[0]; + switch(command) + { + case '!reset-isgm': + if(isGM(msg.playerid) || (tokenized.length>1 && tokenized[1] == IsGMModule.reset_password)) + { + delete state.IsGM; + IsGMModule.CheckInstall(); + sendChat('IsGM','/w gm IsGM data reset.'); + } + else + { + var who=getObj('player',msg.playerid).get('_displayname').split(' ')[0]; + sendChat('IsGM','/w '+who+' ('+who+')Only GMs may reset the IsGM data.' + +'If you are a GM you can reset by specifying the reset password from' + +'the top of the IsGM script as an argument to !reset-isgm') + } + break; + } + } + }, + RegisterEventHandlers: function(){ + on('chat:message',IsGMModule.HandleMessages); + }, + +}; + +on('ready',function(){ + IsGMModule.CheckInstall(); + IsGMModule.RegisterEventHandlers(); +}); + +var isGM = isGM || function(id) { + return IsGMModule.IsGM(id); +}; + diff --git a/package.json b/package.json new file mode 100644 index 0000000000..91df07839a --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "IsGMModule", + "version": "0.6", + "description": "Adds a function, isGM(id), which returns true for gms and false for players. GM database is built in the state object automatically as players and gms send chat messages.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "state.IsGM": "read,write", + "player.displayname": "read", + "player.speakingas": "read" + }, + "conflicts": [ + ] +} From b9e737550fe11d04a385f2b9a2e0ea6c72880be5 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:56 -0600 Subject: [PATCH 11/42] Squashed 'IsGreater/' content from commit 7d2c249 git-subtree-dir: IsGreater git-subtree-split: 7d2c249f5b5551d85ab4b854879e6ed23d877bdb --- IsGreater.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 +++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 IsGreater.js create mode 100644 package.json diff --git a/IsGreater.js b/IsGreater.js new file mode 100644 index 0000000000..f1ff807c8f --- /dev/null +++ b/IsGreater.js @@ -0,0 +1,55 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/IsGreater/IsGreater.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var IsGreater = IsGreater || (function() { + 'use strict'; + + var version = 0.1, + + HandleInput = function(msg) { + var args; + + if (msg.type !== "api") { + return; + } + + if(_.has(msg,'inlinerolls')){ + msg.content = _.chain(msg.inlinerolls) + .reduce(function(m,v,k){ + m['$[['+k+']]']=v.results.total || 0; + return m; + },{}) + .reduce(function(m,v,k){ + return m.replace(k,v); + },msg.content) + .value(); + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + + case '!isgreater': + if(args[1] > args[2]) { + sendChat('','Result: Success'); + } else { + sendChat('','Result: Failure'); + } + break; + } + }, + + registerEventHandlers = function() { + on('chat:message', HandleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + IsGreater.RegisterEventHandlers(); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..8e19cd4322 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "IsGreater", + "version": "0.1", + "description": "Trivial little script to check if the first value is greater than the second and report it to chat.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + }, + "conflicts": [ + ] +} From be163c678d3cf7485c8fa6bd946171beb7fae71f Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:57 -0600 Subject: [PATCH 12/42] Squashed 'ManualAttribute/' content from commit 5c89036 git-subtree-dir: ManualAttribute git-subtree-split: 5c89036fff59240f148b0878885a57a3ddddcc6c --- ManualAttribute.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++ pacakge.json | 13 ++++++++ 2 files changed, 94 insertions(+) create mode 100644 ManualAttribute.js create mode 100644 pacakge.json diff --git a/ManualAttribute.js b/ManualAttribute.js new file mode 100644 index 0000000000..d1fbfa720e --- /dev/null +++ b/ManualAttribute.js @@ -0,0 +1,81 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/ManualAttribute/ManualAttribute.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var ManualAttribute = ManualAttribute || (function() { + 'use strict'; + + var version = 0.1, + autoCalcStatName = 'hp-current', + manualStatName = 'manual_HP', + barNumber = 3, + + fixNewObj= function(obj) { + var p = obj.changed._fbpath, + new_p = p.replace(/([^\/]*\/){4}/, "/"); + obj.fbpath = new_p; + return obj; + }, + + setupManualAttribute = function(token) { + var attr = findObjs({type: 'attribute', characterid: token.get('represents'), name: manualStatName })[0], + srcHP = getAttrByName(token.get('represents'), autoCalcStatName, 'max'), + up = {}; + + if(!attr) { + attr=fixNewObj(createObj('attribute', { + name: manualStatName, + characterid: token.get('represents'), + current: srcHP, + max: srcHP + })); + } else { + attr.set({ + current: srcHP, + max: srcHP + }); + } + up["bar"+barNumber+"_link"] = attr.id + token.set(up); + }, + + handleInput = function(msg) { + var args; + + if (msg.type !== "api") { + return; + } + + args = msg.content.split(/\s+/); + switch(args.shift()) { + case '!setup-manual': + _.chain(msg.selected) + .map(function(o){ + return getObj('graphic',o._id); + }) + .reject(_.isUndefined) + .reject(function(t){ + return undefined === t.get('represents'); + }) + .each(setupManualAttribute) + ; + break; + } + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function() { + 'use strict'; + + ManualAttribute.RegisterEventHandlers(); + log('ManualAttribute: Ready!'); +}); diff --git a/pacakge.json b/pacakge.json new file mode 100644 index 0000000000..2d7340f355 --- /dev/null +++ b/pacakge.json @@ -0,0 +1,13 @@ +{ + "name": "ManualAttribute", + "version": "0.1", + "description": "Creates a manual copy of an autocalculated field, and assigns it to the bar of a token.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + }, + "conflicts": [ + ] +} From 2dbea999416e2f53f008eb5f860393d18522bb1e Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:58 -0600 Subject: [PATCH 13/42] Squashed 'Mark/' content from commit 92f1be0 git-subtree-dir: Mark git-subtree-split: 92f1be07e16bcf0c703856cdfee8d1f313fcced6 --- Mark.js | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 26 +++++ 2 files changed, 295 insertions(+) create mode 100644 Mark.js create mode 100644 package.json diff --git a/Mark.js b/Mark.js new file mode 100644 index 0000000000..425ac3b559 --- /dev/null +++ b/Mark.js @@ -0,0 +1,269 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/Mark/Mark.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var Mark = Mark || (function() { + 'use strict'; + + var version = 0.23, + schemaVersion = 0.2, + markerURL = 'https://s3.amazonaws.com/files.d20.io/images/4994795/7MdfzjgXCkaESbRbxATFSw/thumb.png?1406949835', + + + 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 ' ++'
    ' + +'
    ' + +'Mark v'+version + +'
    ' + +'
    ' + +'

    Mark places a numbered marker under each token whose id is supplied ' + +'to it. Markers are cleared when the Turn Order changes, is closed, ' + +'or when the player page changes. This script is intended to allow players ' + +'to mark their targets for discussion with the GM, usually as part of an ' + +'attack.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!mark '+ch('<')+'Token ID'+ch('>')+' ['+ch('<')+'Token ID'+ch('>')+' ... ]' + +'
    ' + +'

    This command requires a minimum of 1 parameter. For each supplied Token ID, a marker is placed beneath it with a numbered status. The status number starts at 1, increases with each marker placed, and resets the when markers are cleared.

    ' + +'

    Note: If you are using multiple '+ch('@')+ch('{')+'target'+ch('|')+'token_id'+ch('}')+' calls in a macro, and need to mark fewer than the suppled 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('}')+'.' + +'
    • ' + +'
    ' + +'
    ' + +'!mark-clear' + +'
    ' + +'

    Clears all the markers. (GM Only)

    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + reset = function() { + state.Mark.count=0; + _.each(findObjs({ + type: 'graphic', + subtype: 'token', + imgsrc: markerURL + }), function (g){ + g.set({ + layer: 'gmlayer', + width: 70, + height: 70, + top: 35, + left: 35, + statusmarkers: '' + }); + }); + }, + + getStatusForCount = function(count) { + var colorOrder=["red", "blue", "green", "brown", "purple", "pink", "yellow"]; + return _.chain(count.toString().split('')) + .reduce(function(memo,d){ + if(colorOrder.length) { + memo.push(colorOrder.shift()+'@'+d); + } + return memo; + }, []) + .value() + .reverse() + .join(','); + }, + + HandleInput = function(msg) { + var args, + who, + errors=[], + playerPage, + markerSupply, + tokens; + + if (msg.type !== "api" ) { + return; + } + + who=getObj('player',msg.playerid).get('_displayname').split(' ')[0]; + + args = msg.content.split(/ +/); + switch(args[0]) { + case '!mark-clear': + if(isGM(msg.playerid)) { + reset(); + } + break; + + case '!mark': + if(1 === args.length) { + showHelp(); + break; + } + + tokens=_.chain(args) + .rest() + .uniq() + .map(function(a){ + var t=getObj('graphic',a); + if(! t) { + errors.push('Argument ['+a+'] is not a valid token id.'); + } + return t; + },errors) + .filter(function(t){ + return undefined !== t; + }) + .value(); + + if(errors.length) { + sendChat('','/w '+who + +'
    ' + +'
    Error: ' + +errors.join('
    Error: ') + +'
    ' + +'
    ' + ); + } + + // find player's page + if(_.has(Campaign().get('playerspecificpages'),msg.playerid)) { + playerPage = Campaign().get('playerspecificpages')[msg.playerid]; + } else { + playerPage = Campaign().get('playerpageid'); + } + + markerSupply = findObjs({ + type: 'graphic', + subtype: 'token', + imgsrc: markerURL, + layer: 'gmlayer', + pageid: playerPage + }); + _.each(tokens, function (t) { + var m=markerSupply.pop(), + size=( Math.max(t.get('width'), t.get('height') ) * 1.7), + count=++state.Mark.count, + status=getStatusForCount(count); + + if(m) { + m.set({ + width: size, + height: size, + top: t.get('top'), + left: t.get('left'), + layer: 'objects', + statusmarkers: status + }); + } else { + m = fixNewObj(createObj('graphic',{ + imgsrc: markerURL, + subtype: 'token', + pageid: playerPage, + width: size, + height: size, + top: t.get('top'), + left: t.get('left'), + layer: 'objects', + statusmarkers: status + })); + } + toBack(m); + }); + + break; + } + + }, + + CheckInstall = function() { + if( ! _.has(state,'Mark') || state.Mark.version !== schemaVersion) + { + /* Default Settings stored in the state. */ + state.Mark = { + version: schemaVersion, + count: 0, + markedtokens: {} + }; + } + }, + + HandlePlayerPageChange = function() { + reset(); + }, + + HandleTurnOrderChange = function(obj,prev) { + var to = JSON.parse(obj.get("turnorder")), + po = JSON.parse(prev.turnorder); + + if( (_.isArray(to) && _.isArray(po) && !_.isEqual(to[0],po[0]) ) || (_.isArray(to) && ! _.isArray(po)) ) { + reset(); + } + }, + + HandleInitiativePageChange = function(obj) { + if(false === obj.get('initiativepage')) { + reset(); + } + }, + + RegisterEventHandlers = function() { + on('chat:message', HandleInput); + on('change:campaign:playerpageid', HandlePlayerPageChange); + on('change:campaign:turnorder', HandleTurnOrderChange); + on('change:campaign:initiativepage', HandleInitiativePageChange); + }; + + return { + RegisterEventHandlers: RegisterEventHandlers, + CheckInstall: CheckInstall + + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + Mark.CheckInstall(); + Mark.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('Mark requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..3cd787784b --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "Mark", + "version": "0.22", + "description": "Places a numbered Marker under tokens, clears on turn change/close, and page change.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.Mark": "read,write", + "campaign.initiativepage": "read", + "campaign.playerpageid": "read", + "campaign.playherspecificpages": "read", + "campaign.turnorder": "read", + "player.displayname": "read", + "graphic.height": "read,write", + "graphic.layer": "read,write", + "graphic.left": "read,write", + "graphic.statusmarkers": "read,write", + "graphic.top": "read,write", + "graphic.width": "read,write" + }, + "conflicts": [ + ] +} From cbfb1c8c741af4f5236326ebfa96b38d5b0b183c Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:38:59 -0600 Subject: [PATCH 14/42] Squashed 'Measure/' content from commit e1a4252 git-subtree-dir: Measure git-subtree-split: e1a4252d0012eaee0e937bc03a9a9a233d88ecbf --- Measure.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 18 +++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 Measure.js create mode 100644 package.json diff --git a/Measure.js b/Measure.js new file mode 100644 index 0000000000..e1681a9964 --- /dev/null +++ b/Measure.js @@ -0,0 +1,56 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/Measure/Measure.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +// Version: 0.1 + +on('read',function(){ + 'use strict'; + + log('measure: ready'); + on('sendChat',function(msg){ + var args; + + if (msg.type !== "api") { + return; + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!measure': + _.chain(args) + .rest() + .uniq() + .map(function(t){ + return getObj('graphic',t); + }) + .reject(_.isUndefined) + .tap(log) + .reduce(m,function(t){ + if(!m.root){ + m.root=t; + m.msgs.push('Root Node: '+t.get('name')+' ('+t.get('left')+','+t.get('top')+')'); + } else { + var rx = m.root.get('left'), + ry = m.root.get('top'), + rcx =(rx+m.root.get('width')/2), + rcy= (ry+m.root.get('height')/2), + tx = t.get('left'), + ty = t.get('top'), + tcx =(tx+t.get('width')/2), + tcy = (ty+t.get('height')/2), + d = Math.sqrt( Math.pow( (rx-tx),2)+Math.pow( (ry-ty),2)), + cd = Math.sqrt( Math.pow( (rcx-tcx),2)+Math.pow( (rcy-tcy),2)); + m.msgs.push('Root -> '+t.get('name')+': [corner] '+d); + m.msgs.push('Root -> '+t.get('name')+': [center] '+cd); + } + return m; + },{root: false, msgs: []}) + .each(function(o){ + sendChat('Measure','/direct '+o.msgs.join('
    ')); + }); + + break; + } + }); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..9e1977acdb --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "Measure", + "version": "0.1", + "description": "Measure distances between multiple tokens, both from the corners and the center.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "graphic.height": "read", + "graphic.left": "read", + "graphic.name": "read", + "graphic.top": "read", + "graphic.width": "read" + }, + "conflicts": [ + ] +} From 07e4c82b886ddb0b045858b836ee955c551f5b60 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:00 -0600 Subject: [PATCH 15/42] Squashed 'MonsterHitDice/' content from commit 0ebf115 git-subtree-dir: MonsterHitDice git-subtree-split: 0ebf1156ddf0df1a3a172d84d3815de141b94640 --- MonsterHitDice.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 23 ++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 MonsterHitDice.js create mode 100644 package.json diff --git a/MonsterHitDice.js b/MonsterHitDice.js new file mode 100644 index 0000000000..45b6e986ac --- /dev/null +++ b/MonsterHitDice.js @@ -0,0 +1,50 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/MonsterHitDice/MonsterHitDice.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +// Version: 0.1 + +on('ready', function(){ + on('add:graphic',function(obj){ + if( + 'graphic' == obj.get('type') + && 'token' == obj.get('subtype') + && '' != obj.get('represents') + ) + { + setTimeout(_.bind(function(id){ + var obj=getObj('graphic',id) + + if( + undefined != obj + && '' == obj.get('bar2_link') + ) + { + var attrib = findObjs({ + _type: 'attribute', + _characterid:obj.get('represents'), + name: 'HitDice' + }) + if( attrib.length ) + { + sendChat('','/r '+attrib[0].get('current'),function(r){ + var hp=0; + _.each(r,function(subr){ + var val=JSON.parse(subr.content); + if(_.has(val,'total')) + { + hp+=val.total; + } + }); + obj.set({ + bar2_value: hp, + bar2_max: hp + }) + }); + } + } + + },this,obj.id), 100); + } + }); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..bbf029aafd --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "MonsterHitDice", + "version": "", + "description": "Set Monster hit dice on add, usually via drag from journal.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "attribute.current": "read" + "attribute.name": "read", + "attribute.type": "read", + "character.represents": "read", + "graphic.bar2_link": "read", + "graphic.bar2_max": "write", + "graphic.bar2_value": "write", + "graphic.represents": "read", + "graphic.subtype": "read", + "graphic.type": "read" + }, + "conflicts": [ + ] +} From 74cda7623d7f8706a3eaba87d7a974b93a7600e4 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:00 -0600 Subject: [PATCH 16/42] Squashed 'MonsterHitDice5e/' content from commit c2ae699 git-subtree-dir: MonsterHitDice5e git-subtree-split: c2ae699c4bcc8635aef6c610f6fc03456bfa35e6 --- MonsterHitDice5e.js | 93 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 23 +++++++++++ 2 files changed, 116 insertions(+) create mode 100644 MonsterHitDice5e.js create mode 100644 package.json diff --git a/MonsterHitDice5e.js b/MonsterHitDice5e.js new file mode 100644 index 0000000000..f73560ffe6 --- /dev/null +++ b/MonsterHitDice5e.js @@ -0,0 +1,93 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/MonsterHitDice/MonsterHitDice5e.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +on('ready', function(){ + on('add:graphic',function(obj){ + if( + 'graphic' == obj.get('type') + && 'token' == obj.get('subtype') + && '' != obj.get('represents') + ) + { + setTimeout(_.bind(function(id){ + var obj=getObj('graphic',id) + + if( + undefined != obj + && '' == obj.get('bar2_link') + ) + { + var attrib = findObjs({ + _type: 'attribute', + _characterid:obj.get('represents'), + name: 'npc_HP_hit_dice' + }) + if( attrib.length ) + { + sendChat('','/r '+attrib[0].get('current'),function(r){ + var hp=0; + _.each(r,function(subr){ + var val=JSON.parse(subr.content); + if(_.has(val,'total')) + { + hp+=val.total; + } + }); + obj.set({ + bar2_value: hp, + bar2_max: hp + }) + }); + } + } + + },this,obj.id), 100); + } + }); +});/ GIST: https://gist.github.com/shdwjk/7377de58100f4e813432 + +on('ready', function(){ + on('add:graphic',function(obj){ + if( + 'graphic' == obj.get('type') + && 'token' == obj.get('subtype') + && '' != obj.get('represents') + ) + { + setTimeout(_.bind(function(id){ + var obj=getObj('graphic',id) + + if( + undefined != obj + && '' == obj.get('bar2_link') + ) + { + var attrib = findObjs({ + _type: 'attribute', + _characterid:obj.get('represents'), + name: 'npc_HP_hit_dice' + }) + if( attrib.length ) + { + sendChat('','/r '+attrib[0].get('current'),function(r){ + var hp=0; + _.each(r,function(subr){ + var val=JSON.parse(subr.content); + if(_.has(val,'total')) + { + hp+=val.total; + } + }); + obj.set({ + bar2_value: hp, + bar2_max: hp + }) + }); + } + } + + },this,obj.id), 100); + } + }); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..893cd04dba --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "MonsterHitDice5e", + "version": "", + "description": "Set Monster hit dice on add, usually via drag from journal. Configured for Dungeons and Dragons 5e Character Sheet", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "attribute.current": "read" + "attribute.name": "read", + "attribute.type": "read", + "character.represents": "read", + "graphic.bar2_link": "read", + "graphic.bar2_max": "write", + "graphic.bar2_value": "write", + "graphic.represents": "read", + "graphic.subtype": "read", + "graphic.type": "read" + }, + "conflicts": [ + ] +} From 948118cb595cd7824696eca0ce795bae1f499cab Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:01 -0600 Subject: [PATCH 17/42] Squashed 'MotD/' content from commit b32faa9 git-subtree-dir: MotD git-subtree-split: b32faa97706f70f70a72dfaba9cf7240fa5d421e --- MotD.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 19 +++++++++++ 2 files changed, 113 insertions(+) create mode 100644 MotD.js create mode 100644 package.json diff --git a/MotD.js b/MotD.js new file mode 100644 index 0000000000..c93819c8f2 --- /dev/null +++ b/MotD.js @@ -0,0 +1,94 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/MotD/MotD.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var MotD = MotD || (function() { + 'use strict'; + + var version = 0.1, + motdNoteId, + motdNoteName = 'MotD Note', + motdText, + + fixedCreateObj = (function () { + return function () { + var obj = createObj.apply(this, arguments); + if (obj && !obj.fbpath) { + obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/"); + } + return obj; + }; + }()), + + loadMotDNote = function (text) { + motdText=text; + }, + + createMotDNote = function() { + var motdNote = fixedCreateObj('handout',{ + name: motdNoteName + }); + motdText='Welcome to the Game!'; + motdNote.set('notes',motdText); + motdNoteId = motdNote.id; + }, + + checkInstall = function(callback) { + var motdNote = filterObjs(function(o){ + return ( 'handout' === o.get('type') && motdNoteName === o.get('name') && false === o.get('archived')); + })[0]; + if(motdNote) { + motdNoteId = motdNote.id; + motdNote.get('notes',function(n) { + loadMotDNote(n); + callback(); + }); + } else { + createMotDNote(); + callback(); + } + }, + + handlePlayerLogin = function(obj,prev) { + if( true === obj.get('online') && false === prev._online ) { + setTimeout(function(){ + var who=obj.get('displayname').split(/\s/)[0]; + sendChat('MotD','/w '+who+' ' + +motdText.replace(/%%NAME%%/g,obj.get('displayname')) + ); + },10000); + } + }, + + handleNoteChange = function(obj,prev) { + if(obj.id === motdNoteId) { + obj.get('notes',function(n) { + loadMotDNote(n); + }); + } + }, + handleNoteDestroy = function(obj) { + if(obj.id === motdNoteId) { + createMotDNote(); + } + }, + + registerEventHandlers = function() { + on('change:player:_online', handlePlayerLogin); + on('change:handout', handleNoteChange); + on('destroy:handout', handleNoteDestroy); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on('ready',function() { + 'use strict'; + + MotD.CheckInstall(function(){ + MotD.RegisterEventHandlers(); + }); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..b8941e9985 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "Message of the Day", + "version": "0.1", + "description": "Greets players that log in with the contents of a particular note.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "handout.archived": "read", + "handout.name": "read", + "handout.notes": "read,write", + "handout.type": "read", + "player.displayname": "read", + "player.online": "read" + }, + "conflicts": [ + ] +} From d94fc5c0f737ca4b99b3ba529395bbedcdeaaa6e Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:02 -0600 Subject: [PATCH 18/42] Squashed 'MovePlayers/' content from commit 3142f60 git-subtree-dir: MovePlayers git-subtree-split: 3142f60d7be8de412eea58ab1cd27ad28de90470 --- MovePlayers.js | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 ++++++++ 2 files changed, 123 insertions(+) create mode 100644 MovePlayers.js create mode 100644 package.json diff --git a/MovePlayers.js b/MovePlayers.js new file mode 100644 index 0000000000..14e655bb3f --- /dev/null +++ b/MovePlayers.js @@ -0,0 +1,107 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/MovePlayers/MovePlayers.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var MovePlayers = MovePlayers || (function() { + 'use strict'; + + var version = 0.1, + + 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 ' ++'
    ' + +'
    ' + +'MovePlayers v'+version + +'
    ' + +'
    ' + +'

    MovePlayers provides facilities for moving your players around at will.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!move-players-to-same-page-as '+ch('<')+'Token ID'+ch('>')+'' + +'
    ' + +'

    Moves the Players ribbon to the same page as the token identified by Token ID.

    ' + +'
      ' + +'
    • ' + +''+ch('<')+'Token ID'+ch('>')+' '+ch('-')+' The token_id for the token whose page the players should be moved to.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + handleInput = function(msg) { + var args, obj; + + if (msg.type !== "api" || !isGM(msg.playerid)) { + return; + } + + args = msg.content.split(" "); + switch(args[0]) { + case '!move-players-to-same-page-as': + if('--help' === args[1] || args.length < 2) { + showHelp(); + return; + } + + obj=getObj('graphic',args[1]); + + if(!obj) { + sendChat('','/w gm No token found for id: '+args[1]); + } else { + Campaign().set({ + playerpageid: obj.get('pageid') + }); + } + break; + } + + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + MovePlayers.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('MovePlayers requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..143d65947c --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "MovePlayers", + "version": "0.1", + "description": "Allows macros to move the player ribbon for players.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "campaign.playerpageid": "write", + "graphic.pageid": "read" + }, + "conflicts": [ + ] +} From e561fba305e02247995467ce242a17549a364452 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:03 -0600 Subject: [PATCH 19/42] Squashed 'RandomDepth/' content from commit c006dac git-subtree-dir: RandomDepth git-subtree-split: c006dacc982e0905801c9a19ca0adc1a134fd76c --- RandomDepth.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 14 ++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 RandomDepth.js create mode 100644 package.json diff --git a/RandomDepth.js b/RandomDepth.js new file mode 100644 index 0000000000..b64fee780c --- /dev/null +++ b/RandomDepth.js @@ -0,0 +1,58 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/RandomDepth/RandomDepth.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var RandomDepth = RandomDepth || (function() { + 'use strict'; + + var version = 0.2, + + randomToFrontList = function (l) { + if( l.length ) { + var i = randomInteger(l.length)-1; + toFront(l[i]); + if( l.length > 1) { + setTimeout(_.partial(randomToFrontList,_.without(l,l[i])),5); + } + } + }, + + handleMessages = function(msg) + { + if('api' !== msg.type ) { + return; + } + + var args = msg.content.split(/\s+/), + objs; + + switch(args.shift()) + { + case '!random-depth': + objs = _.chain(msg.selected) + .map(function(o){ + return getObj(o._type,o._id); + }) + .reject(_.isUndefined) + .value(); + + randomToFrontList(objs); + break; + } + }, + + registerEventHandlers = function(){ + on('chat:message',handleMessages); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function(){ + 'use strict'; + + RandomDepth.RegisterEventHandlers(); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..3614e5178c --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "RandomDepth", + "version": "0.2", + "description": "Randomly adjusts the depth of selected tokens.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "page.zorder": "write" + }, + "conflicts": [ + ] +} From 45f0b4c72b1980f4a49bed13b71d08f172a208f7 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:04 -0600 Subject: [PATCH 20/42] Squashed 'RandomRotate/' content from commit 3fb5db3 git-subtree-dir: RandomRotate git-subtree-split: 3fb5db3a01cd9e8c178e93e51ce108e8aa489c2e --- RandomRotate.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 ++++++++ 2 files changed, 123 insertions(+) create mode 100644 RandomRotate.js create mode 100644 package.json diff --git a/RandomRotate.js b/RandomRotate.js new file mode 100644 index 0000000000..7c1f57cab6 --- /dev/null +++ b/RandomRotate.js @@ -0,0 +1,107 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/RandomRotate/RandomRotate.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var RandomRotate = RandomRotate || (function(){ + 'use strict'; + + var version = 0.1, + + 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 ' ++'
    ' + +'
    ' + +'RandomRotate v'+version + +'
    ' + +'
    ' + +'
    ' + +'

    Allows the GM to easily randomize the rotation of the selected tokens.

    ' + +'
    ' + +'Commands' + +'
    !random-rotate' + +'
    ' + +'Adjusts the rotation of all the selected tokens to a random angle.' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + + handleInput = function(msg) { + var args; + + if ( "api" !== msg.type || !isGM(msg.playerid) ) { + return; + } + + args = msg.content.split(/\s+/); + + switch(args[0]) { + case '!random-rotate': + if(!( msg.selected && msg.selected.length > 0 ) ) { + showHelp(); + return; + } + + _.chain(msg.selected) + .map(function (o) { + return getObj(o._type,o._id); + }) + .filter(function(o){ + return 'token' === o.get('subtype'); + }) + .each(function(o){ + o.set({rotation: (randomInteger(360)-1)}); + }) + ; + break; + + } + + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + RandomRotate.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('RandomRotate requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f2494e3506 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "RandomRotate", + "version": "0.1", + "description": "Allows the GM to easily rotate all selected tokens to a random angle.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "graphic.rotation": "write", + "graphic.subtype": "read" + }, + "conflicts": [ + ] +} From 6b36bfa386c27f68a58036e73a5ee47271d4deea Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:04 -0600 Subject: [PATCH 21/42] Squashed 'SizeLock/' content from commit 7d2421d git-subtree-dir: SizeLock git-subtree-split: 7d2421d8c6d2382a0f7758b357866cb95b81747b --- SizeLock.js | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 +++++++ 2 files changed, 168 insertions(+) create mode 100644 SizeLock.js create mode 100644 package.json diff --git a/SizeLock.js b/SizeLock.js new file mode 100644 index 0000000000..cf55b49688 --- /dev/null +++ b/SizeLock.js @@ -0,0 +1,148 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/SizeLock/SizeLock.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var SizeLock = SizeLock || (function() { + 'use strict'; + + var version = 0.1, + schemaVersion = 0.1, + + performLock = function() { + if( ! state.SizeLock.locked ) { + state.SizeLock.locked = true; + } + sendChat('SizeLock','/w gm ' + +'
    ' + +'Token sizes are now Locked.' + +'
    ' + ); + }, + performUnlock = function() { + if( state.SizeLock.locked ) { + state.SizeLock.locked = false; + } + sendChat('SizeLock','/w gm ' + +'
    ' + +'Token sizes are now Unlocked.' + +'
    ' + ); + }, + showHelp = function() { + var stateColor = (state.SizeLock.locked) ? ('#990000') : ('#009900'), + stateName = (state.SizeLock.locked) ? ('Locked') : ('Unlocked'); + + sendChat('', + '/w gm ' ++'
    ' + +'
    ' + +'
    '+stateName+'
    ' + +'SizeLock v'+version + +'
    ' + +'
    ' + +'
    ' + +'

    SizeLock allows the GM to toggle the Campaign into a state where ' + +'any time a token is resized, the change is reverted automatically.' + +'

    ' + +'Commands' + +'
    !size-lock' + +'
    ' + +'Executing the command with no arguments prints this help. The following arguments may be supplied in order to change the configuration. All changes are persisted between script restarts.' + +'
      ' + +'
    • ' + +'lock -- Locks the size of all tokens, reverting any changes automatically.' + +'
    • ' + +'
    • ' + +'unlock -- Unlocks the size of all tokens, allowing them to be resized.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + HandleInput = function(msg) { + var args; + + if (msg.type !== "api" || !isGM(msg.playerid) ) { + return; + } + + + args = msg.content.split(" "); + switch(args[0]) { + case '!size-lock': + switch(args[1]) { + case 'lock': + performLock(); + break; + case 'unlock': + performUnlock(); + break; + default: + showHelp(); + break; + } + break; + } + + }, + + HandleResize = function(obj,prev) { + if(state.SizeLock.locked + && 'token' === obj.get('subtype') + && ( obj.get('width') !== prev.width || obj.get('height') !== prev.height ) ) { + obj.set({ + width: prev.width, + height: prev.height, + top: prev.top, + left: prev.left + }); + } + }, + + CheckInstall = function() { + if( ! _.has(state,'SizeLock') || state.SizeLock.version !== SizeLock.schemaVersion) + { + /* Default Settings stored in the state. */ + state.SizeLock = { + version: SizeLock.schemaVersion, + locked: false + }; + } + }, + + RegisterEventHandlers = function() { + on('chat:message', HandleInput); + on('change:graphic', HandleResize); + }; + + return { + RegisterEventHandlers: RegisterEventHandlers, + CheckInstall: CheckInstall + }; +}()); + +on("ready",function(){ + 'use strict'; + + var Has_IsGM=false; + try { + _.isFunction(isGM); + Has_IsGM=true; + } + catch (err) + { + log('--------------------------------------------------------------'); + log('SizeLock requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } + + if( Has_IsGM ) + { + SizeLock.CheckInstall(); + SizeLock.RegisterEventHandlers(); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f15f5b45f8 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "SizeLock", + "version": "0.1", + "description": "Toggles a state which reverts any size changes automatically.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.SizeLock": "read,write", + "graphic.height": "read,write", + "graphic.left": "write", + "graphic.subtype": "read", + "graphic.top": "write", + "graphic.width": "read,write" + }, + "conflicts": [ + ] +} From 478e94984f20b3c60c847a2c137159f58a5c3dad Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:06 -0600 Subject: [PATCH 22/42] Squashed 'SpellLevel5e/' content from commit eb49f34 git-subtree-dir: SpellLevel5e git-subtree-split: eb49f34925d421ca9aafe6eab73e407111480455 --- SpellLevel5e.js | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ pacakge.json | 15 +++++ 2 files changed, 166 insertions(+) create mode 100644 SpellLevel5e.js create mode 100644 pacakge.json diff --git a/SpellLevel5e.js b/SpellLevel5e.js new file mode 100644 index 0000000000..6ac70fad66 --- /dev/null +++ b/SpellLevel5e.js @@ -0,0 +1,151 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/SpellLevel5e/SpellLevel5e.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var SpellLevel5e = SpellLevel5e || (function() { + 'use strict'; + + var version = 0.1, + schemaVersion = 0.1, + attrActions = { + bard_level: ['cantripDice'], + cleric_level: ['cantripDice'], + druid_level: ['cantripDice'], + paladin_level: ['cantripDice'], + ranger_level: ['cantripDice'], + sorcerer_level: ['cantripDice'], + wizard_level: ['cantripDice'] + }, + attrOperations = { + cantripDice: function(level) { + return Math.round( (parseInt(level,10)+4)/6, 0); + } + }, + + + checkInstall = function() { + var allAttrs = findObjs({type:'attribute'}), + attrNames = _.chain(attrActions) + .map(function(o,k){ + return _.map(o,function(n){ + return k.replace(/level$/,n); + }); + }) + .flatten() + .value(), + charMap = _.chain(allAttrs) + .filter(function(o) { + return _.contains(attrNames,o.get('name')); + }) + .reduce(function(m,o){ + if(! _.has(m,o.get('characterid'))){ + m[o.get('characterid')]={}; + } + m[o.get('characterid')][o.get('name')]=o; + return m; + },{}) + .value(); + + log('SpellLevel5e: Checking Spell Level Attributes.'); + + _.chain(allAttrs) + .filter(function(o){ + return _.has(attrActions,o.get('name')); + }) + .each(function(o){ + var ca = charMap[o.get('characterid')] || {}; + _.each(attrActions[o.get('name')],function(fname){ + var cai = o.get('name').replace(/level$/,fname), + v = (attrOperations[fname] && attrOperations[fname](o.get('current')) ) || 0; + if(_.has(ca,cai)){ + if( ca[cai].get('current') !== v) { + log('SpellLevel5e: ...Updating '+cai+' for Character ID: '+o.get('characterid')); + ca[cai].set({current: v}); + } + } else { + log('SpellLevel5e: ...Creating '+cai+' for Character ID: '+o.get('characterid')); + createObj('attribute', { + characterid: o.get('characterid'), + name: cai, + current: v, + max: '' + }); + } + }); + }); + + log('SpellLevel5e: Spell Level Attributes Checked.'); + }, + handleAttributeEvent = function(obj) { + var name = obj.get('name'), + attrNames, + attrMap; + if(_.has(attrActions, name)){ + attrNames = _.map(attrActions[name],function(n){ + return name.replace(/level$/,n); + }); + attrMap = _.chain(findObjs({type:'attribute'})) + .filter(function(o) { + return (_.contains(attrNames,o.get('name')) && (o.get('characterid') === obj.get('characterid')) ); + }) + .reduce(function(m,o){ + m[o.get('name')]=o; + return m; + },{}) + .value(); + + _.each(attrActions[name],function(fname){ + var cai = name.replace(/level$/,fname), + v = (attrOperations[fname] && attrOperations[fname](obj.get('current')) ) || 0; + if(_.has(attrMap,cai)){ + if( attrMap[cai].get('current') !== v) { + log('SpellLevel5e: Updating '+cai+' for Character ID: '+obj.get('characterid')); + attrMap[cai].set({current: v}); + } + } else { + log('SpellLevel5e: Creating '+cai+' for Character ID: '+obj.get('characterid')); + createObj('attribute', { + characterid: obj.get('characterid'), + name: cai, + current: v, + max: '' + }); + } + }); + + } + }, + + handleInput = function(msg) { + var args; + + if (msg.type !== "api") { + return; + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!SpellLevel5e': + break; + } + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + on('add:attribute', handleAttributeEvent); + on('change:attribute:current', handleAttributeEvent); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; + +}()); + +on('ready',function() { + 'use strict'; + + SpellLevel5e.CheckInstall(); + SpellLevel5e.RegisterEventHandlers(); +}); diff --git a/pacakge.json b/pacakge.json new file mode 100644 index 0000000000..4e49072915 --- /dev/null +++ b/pacakge.json @@ -0,0 +1,15 @@ +{ + "name": "SpellLevel5e", + "version": "0.1", + "description": "", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.SpellLevel5e": "read,write", + }, + "conflicts": [ + ] +} From e8e8bea4a5b275d34c232ac192a4fff3de01dbd4 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:06 -0600 Subject: [PATCH 23/42] Squashed 'SpinTokens/' content from commit 721678b git-subtree-dir: SpinTokens git-subtree-split: 721678b1e7ec88f8e95d7bb401cbd1edbcceb3e0 --- SpinTokens.js | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 ++++++ 2 files changed, 211 insertions(+) create mode 100644 SpinTokens.js create mode 100644 package.json diff --git a/SpinTokens.js b/SpinTokens.js new file mode 100644 index 0000000000..4b574a2ca1 --- /dev/null +++ b/SpinTokens.js @@ -0,0 +1,191 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/SpinTokens/SpinTokens.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var SpinTokens = SpinTokens || (function(){ + 'use strict'; + + var version = 0.3, + schemaVersion = 0.1, + spinInterval = false, + stepRate = 200, + defaultSecondsPerCycle = 20, + millisecondsPerSecond = 1000, + + 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 ' ++'
    ' + +'
    ' + +'SpinTokens v'+version + +'
    ' + +'
    ' + +'
    ' + +'

    Allows the GM to toggle spinning of selected tokens

    ' + +'
    ' + +'Commands' + +'
    !spin-start '+ch('[')+'Seconds Per Cycle'+ch(']')+'' + +'
    ' + +'Starts a selected token spinning, optionally with a speed.' + +'
      ' + +'
    • ' + +'Seconds Per Cycle '+ch('-')+' Specifies the number of seconds for the token to make a full rotation. Using a negative number causes the object to spin counter-clockwise. Default: '+defaultSecondsPerCycle +'
    • ' + +' ' + +'
    ' + +'
    ' + +'
    ' + +'
    !spin-stop' + +'
    ' + +'Stops the selected tokens from spinning.' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + + handleInput = function(msg) { + var args, + secondsPerCycle; + + if ( "api" !== msg.type || !isGM(msg.playerid) ) { + return; + } + + args = msg.content.split(/\s+/); + + switch(args[0]) { + case '!spin-start': + if(!( msg.selected && msg.selected.length > 0 ) ) { + showHelp(); + return; + } + + secondsPerCycle = args[1] || defaultSecondsPerCycle; + _.chain(msg.selected) + .map(function (o) { + return getObj(o._type,o._id); + }) + .filter(function(o){ + return 'token' === o.get('subtype'); + }) + .each(function(o){ + state.SpinTokens.spinners[o.id]={ + id: o.id, + page: o.get('pageid'), + rate: (secondsPerCycle*millisecondsPerSecond) + }; + }) + ; + break; + + case '!spin-stop': + if(!( msg.selected && msg.selected.length > 0 ) ) { + showHelp(); + return; + } + + _.chain(msg.selected) + .map(function (o) { + return getObj(o._type,o._id); + }) + .filter(function(o){ + return 'token' === o.get('subtype'); + }) + .each(function(o){ + delete state.SpinTokens.spinners[o.id]; + }) + ; + break; + } + + }, + + animateRotation = function() { + var pages = _.union([Campaign().get('playerpageid')], _.values(Campaign().get('playerspecificpages'))); + + _.chain(state.SpinTokens.spinners) + .filter(function(o){ + return _.contains(pages,o.page); + }) + .each(function(sdata){ + var s = getObj('graphic',sdata.id); + + if(!s) { + delete state.SpinTokens.spinners[sdata.id]; + } else { + s.set({ + rotation: (( (Date.now()%sdata.rate)/sdata.rate )*360) + }); + } + }); + + }, + + handleTokenDelete = function(obj) { + var found = _.findWhere(state.SpinTokens.spinners, {id: obj.id}); + if(found) { + delete state.SpinTokens.spinners[obj.id]; + } + }, + + + checkInstall = function() { + if( ! _.has(state,'SpinTokens') || state.SpinTokens.version !== schemaVersion) { + log('SpinTokens: Resetting state'); + + state.SpinTokens = { + version: schemaVersion, + spinners: {} + }; + } + + spinInterval = setInterval(animateRotation,stepRate); + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + on('destroy:graphic', handleTokenDelete); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + SpinTokens.CheckInstall(); + SpinTokens.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('SpinTokens requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..2a6d710a70 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "SpinTokens", + "version": "0.3", + "description": "Allows the GM to toggle spinning of selected tokens.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.SpinTokens": "read,write", + "campaign.playerpageid": "read", + "campaign.playerspecificpages": "read", + "graphic.pageid": "read", + "graphic.rotation": "write", + "graphic.subtype": "read", + }, + "conflicts": [ + ] +} From 162c2d1fa39a1ed854ded65f56b3afe93de5b489 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:07 -0600 Subject: [PATCH 24/42] Squashed 'TableExport/' content from commit 0b6c6a3 git-subtree-dir: TableExport git-subtree-split: 0b6c6a39c7b0cbdb686ac6bed4900f19e5fbfd8d --- TableExport.js | 276 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 22 ++++ 2 files changed, 298 insertions(+) create mode 100644 TableExport.js create mode 100644 package.json diff --git a/TableExport.js b/TableExport.js new file mode 100644 index 0000000000..0c0b5d5f22 --- /dev/null +++ b/TableExport.js @@ -0,0 +1,276 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TableExport/TableExport.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var TableExport = TableExport || (function() { + 'use strict'; + + var version = 0.11, + tableCache = {}, + + fixedCreateObj = (function () { + return function () { + var obj = createObj.apply(this, arguments); + if (obj && !obj.fbpath) { + obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/"); + } + 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 ' ++'
    ' + +'
    ' + +'TableExport v'+version + +'
    ' + +'
    ' + +'

    This script dumps commands to the chat for reconstructing a rollable table on another campaign. While this can be done on your own campaigns via the transmogrifyer, this script allows you to pass those commands to a friend and thus share your own creative works with others.

    ' + +'

    Caveat: Avatar images that are not in your own library will be ignored by the API on import, but will not prevent creation of the table and table items.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!export-table --'+ch('<')+'Table Name'+ch('>')+' [ --'+ch('<')+'Table Name'+ch('>')+' ...]' + +'
    ' + +'

    For all table names, case is ignored and you may use partial 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 table names. Exception: An exact match will trump a partial match. In the previous example, if a table named '+ch('"')+'Max'+ch('"')+' existed, it would be the only table matched for --max.

    ' + +'
      ' + +'
    • ' + +'--'+ch('<')+'Table Name'+ch('>')+' '+ch('-')+' This is the name of a table to export. You can specify as many tables as you like in a single command.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + +'
    ' + +'!import-table --'+ch('<')+'Table Name'+ch('>')+' --'+ch('<')+'[ show | hide ]'+ch('>')+'' + +'
    ' + +'

    This is the command output by !export-table to create the new table. You likely will not need issue these commands directly.

    ' + +'
      ' + +'
    • ' + +'--'+ch('<')+'Table Name'+ch('>')+' '+ch('-')+' This is the name of the table to be create.' + +'
    • ' + +'
    • ' + +'--'+ch('<')+'[ show | hide ]'+ch('>')+' '+ch('-')+' This whether to show the table to players or hide it.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + +'
    ' + +'!import-table-item --'+ch('<')+'Table Name'+ch('>')+' --'+ch('<')+'Table Item Name'+ch('>')+' --'+ch('<')+'Weight'+ch('>')+' --'+ch('<')+'Avatar URL'+ch('>')+'' + +'
    ' + +'

    This is the command output by !export-table to create the new table. You likely will not need issue these commands directly.

    ' + +'
      ' + +'
    • ' + +'--'+ch('<')+'Table Name'+ch('>')+' '+ch('-')+' This is the name of the table to add items to. Note: unlike for !export-table, this must be an exact name match to the created table.' + +'
    • ' + +'
    • ' + +'--'+ch('<')+'Table Item Name'+ch('>')+' '+ch('-')+' This is the name of the table item to create. Note: Because the string '+ch('"')+' --'+ch('"')+' may occur in a table item name, you may see '+ch('"')+'[TABLEEXPORT:ESCAPE]'+ch('"')+' show up as a replacement in these commands. This value is corrected internally to the correct '+ch('"')+' --'+ch('"')+' sequence on import.' + +'
    • ' + +'
    • ' + +'--'+ch('<')+'Weight'+ch('>')+' '+ch('-')+' This is the weight for this table item and should be an integer value.' + +'
    • ' + +'
    • ' + +'--'+ch('<')+'Avatar URL'+ch('>')+' '+ch('-')+' This is the URL for the avatar image of the table item.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + handleInput = function(msg) { + var args, matches, tables, tableIDs=[], errors=[], items, itemMatches, accum=''; + + if (msg.type !== "api" || !isGM(msg.playerid)) { + return; + } + + args = msg.content.split(/\s+/); + switch(args[0]) { + case '!import-table': + args = msg.content.split(/\s+--/); + if(args.length === 1) { + showHelp(); + break; + } + if(_.has(tableCache,args[1])) { + sendChat('','/w gm ' + +'
    ' + +'Warning: ' + +'Table ['+args[1]+'] already exists, skipping create.' + +'
    ' + ); + } else { + tableIDs=findObjs({type: 'rollabletable', name: args[1]}); + if(tableIDs.length) { + sendChat('','/w gm ' + +'
    ' + +'Warning: ' + +'Table ['+args[1]+'] already exists, skipping create.' + +'
    ' + ); + } else { + tableIDs=fixedCreateObj('rollabletable',{ + name: args[1], + showplayers: ('show'===args[2]) + }); + tableCache[args[1]]=tableIDs.id; + } + } + break; + + case '!import-table-item': + args = msg.content.split(/\s+--/); + if(args.length === 1) { + showHelp(); + break; + } + args[2] = args[2].replace(/\[TABLEEXPORT:ESCAPE\]/g, ' --'); + if(!_.has(tableCache,args[1])) { + tableIDs=findObjs({type: 'rollabletable', name: args[1]}); + if(!tableIDs.length) { + sendChat('','/w gm ' + +'
    ' + +'Error: ' + +'Table ['+args[1]+'] doesn not exist. Cannot create table item.' + +'
    ' + ); + break; + } else { + tableCache[args[1]]=tableIDs[0].id; + } + } + fixedCreateObj('tableitem',{ + name: args[2], + rollabletableid: tableCache[args[1]], + weight: parseInt(args[3],10), + avatar: args[4] + }); + break; + + case '!export-table': + args = msg.content.split(/\s+--/); + if(args.length === 1) { + showHelp(); + break; + } + tables=findObjs({type: 'rollabletable'}); + matches=_.chain(args) + .rest() + .map(function(n){ + var l=_.filter(tables,function(t){ + return t.get('name').toLowerCase() === n.toLowerCase(); + }); + return ( 1 === l.length ? l : _.filter(tables,function(t){ + return -1 !== t.get('name').toLowerCase().indexOf(n.toLowerCase()); + })); + }) + .value(); + + _.each(matches,function(o,idx){ + if(1 !== o.length) { + if(o.length) { + errors.push('Rollable Table ['+args[idx+1]+'] is ambiguous and matches '+o.length+' names: '+_.map(o,function(e){ + return e.get('name'); + }).join(', ')+''); + } else { + errors.push('Rollable Table ['+args[idx+1]+'] does not match any names.'); + } + } + },errors); + + if(errors.length) { + sendChat('','/w gm ' + +'
    ' + +'
    Error: ' + +errors.join('
    Error: ') + +'
    ' + +'
    ' + ); + break; + } + + if( ! errors.length) { + matches=_.chain(matches) + .flatten(true) + .map(function(t){ + tableIDs.push(t.id); + return t; + }) + .value(); + + items=findObjs({type: 'tableitem'}); + itemMatches=_.chain(items) + .filter(function(i){ + return _.contains(tableIDs,i.get('rollabletableid')); + }) + .reduce(function(memo,e){ + if(!_.has(memo,e.get('rollabletableid'))) { + memo[e.get('rollabletableid')]=[e]; + } else { + memo[e.get('rollabletableid')].push(e); + } + return memo; + },{}) + .value(); + _.each(matches, function(t){ + accum+='!import-table --'+t.get('name')+' --'+(t.get('showplayers') ? 'show' : 'hide')+'
    '; + _.each(itemMatches[t.id], function(i){ + accum+='!import-table-item --'+t.get('name')+' --'+i.get('name').replace(/ --/g,'[TABLEEXPORT:ESCAPE]')+' --'+i.get('weight')+' --'+i.get('avatar')+'
    '; + }); + }); + sendChat('', '/w gm '+accum); + + } + break; + + + } + + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; +}()); + + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + TableExport.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('TableExport requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..b623aa03a8 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "TableExport", + "version": "0.11", + "description": "A script for exporting Rollable Tables between accounts.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "rollabletable": "create", + "rollabletable.name": "read", + "rollabletable.showplayers": "read", + "tableitem": "create", + "tableitem.avatar": "read", + "tableitem.name": "read", + "tableitem.rollabletableid": "read", + "tableitem.weight": "read" + }, + "conflicts": [ + ] +} From 7a7745021e74373eb1202d226ddc68ace457c0e0 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:08 -0600 Subject: [PATCH 25/42] Squashed 'TableTokenSizer/' content from commit b809e4a git-subtree-dir: TableTokenSizer git-subtree-split: b809e4a2f7d112219be86cc1921937224fc3777c --- TableTokenSizer.js | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 17 +++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 TableTokenSizer.js create mode 100644 package.json diff --git a/TableTokenSizer.js b/TableTokenSizer.js new file mode 100644 index 0000000000..c441bff227 --- /dev/null +++ b/TableTokenSizer.js @@ -0,0 +1,38 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TableTokenSizer/TableTokenSizer.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var TableTokenSizer = TableTokenSizer || (function() { + 'use strict'; + + var version = 0.1, + gridSize = 70, + scaleSize = 3, + + sizeTableToken = function(obj, prev) { + if(obj.get('sides')) { + if( (gridSize * scaleSize) !== obj.get('width') ) { + obj.set({ + width: (gridSize * scaleSize), + height: (gridSize * scaleSize), + isdrawing: false + }); + } + } + }, + + registerEventHandlers = function() { + on('add:graphic', sizeTableToken); + }; + + return { + RegisterEventHandlers: registerEventHandlers + }; +}()); + + +on("ready",function(){ + 'use strict'; + + TableTokenSizer.RegisterEventHandlers(); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..5129e7e232 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "TableTokenSizer", + "version": "0.1", + "description": "Simple script to scale rollable tokens added to the tabletop to a given size (defaults to 3x3) an set them as not drawings.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "graphic.height": "write", + "graphic.isdrawing": "write" + "graphic.sides": "read", + "graphic.width": "read,write" + }, + "conflicts": [ + ] +} From 0c17eb6a7cb509df36f2533f4c4d000140e50f43 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:09 -0600 Subject: [PATCH 26/42] Squashed 'TempHPAndStatus/' content from commit e097989 git-subtree-dir: TempHPAndStatus git-subtree-split: e0979892385a523219b298cf940e2d2d5354410c --- TempHPAndStatus.js | 112 +++++++++++++++++++++++++++++++++++++++++++++ foo | 16 +++++++ package.json | 22 +++++++++ 3 files changed, 150 insertions(+) create mode 100644 TempHPAndStatus.js create mode 100644 foo create mode 100644 package.json diff --git a/TempHPAndStatus.js b/TempHPAndStatus.js new file mode 100644 index 0000000000..0b24f8e47a --- /dev/null +++ b/TempHPAndStatus.js @@ -0,0 +1,112 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TempHPAndStatus/TempHPAndStatus.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var TempHPAndStatus = TempHPAndStatus || (function() { + 'use strict'; + + var version = 0.2, + HitPointBarNum = 3, + TempHitPointsIn = 'temp_HP', + + BloodiedMarker = 'half-heart', + DyingMarker = 'dead', + DeadMarker = 'broken-skull', + ASSUME_HEALS = true, + + + CurrentHPLocation = 'bar' + HitPointBarNum + '_value', + MaxHPLocation = 'bar' + HitPointBarNum + '_max', + + TokenChange = function(obj, prev) { + if (obj.get("isdrawing")) { + return; + } + + var HP = { + now: parseInt(obj.get(CurrentHPLocation),10) || 0, + old: parseInt(prev[CurrentHPLocation],10) || 0, + max: parseInt(obj.get(MaxHPLocation),10) || 0, + tmp: 0 + }, + tmpHPInAttr = false, + tmpHPAttr, + target = {}; + + if (0 === HP.max) { + return; + } + + if(obj.get(TempHitPointsIn)) { + HP.tmp = parseInt(obj.get(TempHitPointsIn),10) || 0; + } else if(obj.get('represents')) { + tmpHPInAttr = true; + tmpHPAttr = findObjs({_type: 'attribute', _characterid: obj.get('represents'), name: TempHitPointsIn})[0]; + if(tmpHPAttr) { + HP.tmp = parseInt(tmpHPAttr.get('current'),10) || 0; + } + } + + HP.bloodied = Math.floor(HP.max/2) || 0; + HP.dead = -HP.bloodied; + HP.delta = HP.now-HP.old; + HP.healed = (HP.delta > 0); + HP.hurt = (HP.delta <0); + + if ( 0 !== HP.delta ) { + if (HP.tmp && (HP.delta < 0 )) { + target[TempHitPointsIn] = Math.max( HP.tmp + HP.delta, 0 ); + HP.delta = Math.min( HP.delta + HP.tmp, 0 ); + target[CurrentHPLocation] = Math.min( HP.old + HP.delta, HP.max ); + HP.now = target[CurrentHPLocation]; + HP.tmp = target[TempHitPointsIn]; + } else if (HP.now > HP.max) { + HP.now=HP.max; + target[CurrentHPLocation]=HP.max; + } else if( ASSUME_HEALS && HP.old < 0 && (HP.delta > 0)) { + HP.now=Math.min( HP.delta, HP.max ); + target[CurrentHPLocation]=HP.now; + } + + if ( HP.now <= HP.dead ) { + HP.now=HP.dead; + target[CurrentHPLocation]=HP.dead; + target['status_'+DeadMarker]=true; + } else { + target['status_'+DeadMarker]=false; + } + + if ( HP.now <= 0 && HP.now > HP.dead ) { + target['status_'+DyingMarker]=true; + } else { + target['status_'+DyingMarker]=false; + } + + if ( HP.now <= HP.bloodied && HP.now > HP.dead ) { + target['status_'+BloodiedMarker]=true; + } else { + target['status_'+BloodiedMarker]=false; + } + if(tmpHPInAttr) { + if(tmpHPAttr && undefined !== target[TempHitPointsIn] ) { + tmpHPAttr.set({current: (target[TempHitPointsIn] || 0)}); + } + delete target[TempHitPointsIn]; + } + obj.set(target); + } + }, + + RegisterEventHandlers = function() { + on('change:token', TokenChange); + }; + + return { + RegisterEventHandlers: RegisterEventHandlers + }; +}()); + +on('ready',function(){ + 'use strict'; + TempHPAndStatus.RegisterEventHandlers(); +}); diff --git a/foo b/foo new file mode 100644 index 0000000000..87a509449d --- /dev/null +++ b/foo @@ -0,0 +1,16 @@ + +var lights = function(radius,dim) { + _.each(findObjs({ +_type: 'graphic', +name: "Torch" +}), function(light){ + light.set("light_radius", radius); + light.set("light_dimradius", dim); + }); +}; +on("chat:message", function(msg) { + if(msg.type == "api" && msg.content.indexOf("!lightson") !== -1) { + lights(30,20);};}); +on("chat:message", function(msg) { + if(msg.type == "api" && msg.content.indexOf("!lightsoff") !== -1) { + lights(0,0);};}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..9caedc175f --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "TempHPAndStatus", + "version": "0.2", + "description": "Temp hit point manager and bloodied/dying/dead status markers.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "attribute.current": "read,write", + "attribute.name": "read", + "attribute.type": "read", + "graphic.bar1_value": "read,write", + "graphic.bar2_value": "read,write", + "graphic.bar3_value": "read,write", + "graphic.isdrawing": "read", + "graphic.represents": "read", + "graphic.status_*": "read,write" + }, + "conflicts": [ + ] +} From 8a125c049147e69e6f612a07e7260c0b0c045f31 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:10 -0600 Subject: [PATCH 27/42] Squashed 'TokenLock/' content from commit 5266915 git-subtree-dir: TokenLock git-subtree-split: 526691511f61aef1ec042944f5f2ef1d59b0a81b --- TokenLock.js | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 22 +++++++ 2 files changed, 180 insertions(+) create mode 100644 TokenLock.js create mode 100644 package.json diff --git a/TokenLock.js b/TokenLock.js new file mode 100644 index 0000000000..fa83ec74e8 --- /dev/null +++ b/TokenLock.js @@ -0,0 +1,158 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TokenLock/TokenLock.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var TokenLock = TokenLock || (function() { + 'use strict'; + + var version = 0.11, + schemaVersion = 0.1, + + performLock = function() { + if( ! state.TokenLock.locked ) { + state.TokenLock.locked = true; + } + sendChat('TokenLock','/w gm ' + +'
    ' + +'Tokens are now Locked.' + +'
    ' + ); + }, + performUnlock = function() { + if( state.TokenLock.locked ) { + state.TokenLock.locked = false; + } + sendChat('TokenLock','/w gm ' + +'
    ' + +'Tokens are now Unlocked.' + +'
    ' + ); + }, + showHelp = function() { + var stateColor = (state.TokenLock.locked) ? ('#990000') : ('#009900'), + stateName = (state.TokenLock.locked) ? ('Locked') : ('Unlocked'); + + sendChat('', + '/w gm ' ++'
    ' + +'
    ' + +'
    '+stateName+'
    ' + +'TokenLock v'+version + +'
    ' + +'
    ' + +'
    ' + +'

    TokenLock allows the GM to selectively prevent players from moving their tokens. ' + +'Since change:graphic events to not specify who changed the ' + +'graphic, determination of player tokens is based on whether that token ' + +'has an entry in the controlled by field of either the token or ' + +'the character it represents. If controlled by is empty, the ' + +'GM can freely move the token at any point. If there is any entry in ' + +'controlled by, the token can only be moved when TokenLock is ' + +'unlocked.

    ' + + '

    Moving of player controlled cards is still permissible.

    ' + +'
    ' + +'Commands' + +'
    !tl' + +'
    ' + +'Executing the command with no arguments prints this help. The following arguments may be supplied in order to change the configuration. All changes are persisted between script restarts.' + +'
      ' + +'
    • ' + +'lock -- Locks the player tokens to prevent moving them.' + +'
    • ' + +'
    • ' + +'unlock -- Unlocks the player tokens allowing them to be moved.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + HandleInput = function(msg) { + var args; + + if (msg.type !== "api" || !isGM(msg.playerid) ) { + return; + } + + + args = msg.content.split(" "); + switch(args[0]) { + case '!tl': + switch(args[1]) { + case 'lock': + performLock(); + break; + case 'unlock': + performUnlock(); + break; + default: + showHelp(); + break; + } + break; + } + + }, + + HandleMove = function(obj,prev) { + if(state.TokenLock.locked + && 'token' === obj.get('subtype') + && ( obj.get('left') !== prev.left || obj.get('top') !== prev.top || obj.get('rotation') !== prev.rotation ) + ) { + if('' !== obj.get('controlledby')) { + obj.set({left: prev.left, top: prev.top, rotation: prev.rotation}); + } else if('' !== obj.get('represents') ) { + var character = getObj('character',obj.get('represents')); + if( character && character.get('controlledby') ) { + obj.set({left: prev.left, top: prev.top, rotation: prev.rotation}); + } + } + } + }, + + CheckInstall = function() { + if( ! _.has(state,'TokenLock') || state.TokenLock.version !== TokenLock.schemaVersion) + { + /* Default Settings stored in the state. */ + state.TokenLock = { + version: TokenLock.schemaVersion, + locked: false + }; + } + }, + + RegisterEventHandlers = function() { + on('chat:message', HandleInput); + on('change:graphic', HandleMove); + }; + + return { + RegisterEventHandlers: RegisterEventHandlers, + CheckInstall: CheckInstall + }; +}()); + +on("ready",function(){ + 'use strict'; + + var Has_IsGM=false; + try { + _.isFunction(isGM); + Has_IsGM=true; + } + catch (err) + { + log('--------------------------------------------------------------'); + log('TokenLock requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } + + if( Has_IsGM ) + { + TokenLock.CheckInstall(); + TokenLock.RegisterEventHandlers(); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..40213c757a --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "TokenLock", + "version": "0.11", + "description": "Allows GMs to selectively lock the movement of Player Tokens.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.TokenLock": "read,write", + "character.controlledby": "read", + "graphic.controlledby": "read", + "graphic.left": "read,write", + "graphic.represents": "read", + "graphic.rotation": "read,write", + "graphic.subtype": "read", + "graphic.top": "read,write" + }, + "conflicts": [ + ] +} From 94a84190d6ff9bc3609ad699b8b837e7e008a709 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:10 -0600 Subject: [PATCH 28/42] Squashed 'TokenMod/' content from commit a296b22 git-subtree-dir: TokenMod git-subtree-split: a296b22b3353d33ca7b35523bcda2380323f220a --- TokenMod.js | 995 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 24 ++ 2 files changed, 1019 insertions(+) create mode 100644 TokenMod.js create mode 100644 package.json diff --git a/TokenMod.js b/TokenMod.js new file mode 100644 index 0000000000..0845cbd99a --- /dev/null +++ b/TokenMod.js @@ -0,0 +1,995 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TokenMod/TokenMod.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var TokenMod = TokenMod || (function() { + 'use strict'; + + var version = 0.54, + schemaVersion = 0.1, + + fields = { + // booleans + showname: {type: 'boolean'}, + showplayers_name: {type: 'boolean'}, + showplayers_bar1: {type: 'boolean'}, + showplayers_bar2: {type: 'boolean'}, + showplayers_bar3: {type: 'boolean'}, + showplayers_aura1: {type: 'boolean'}, + showplayers_aura2: {type: 'boolean'}, + playersedit_name: {type: 'boolean'}, + playersedit_bar1: {type: 'boolean'}, + playersedit_bar2: {type: 'boolean'}, + playersedit_bar3: {type: 'boolean'}, + playersedit_aura1: {type: 'boolean'}, + playersedit_aura2: {type: 'boolean'}, + light_otherplayers: {type: 'boolean'}, + light_hassight: {type: 'boolean'}, + isdrawing: {type: 'boolean'}, + flipv: {type: 'boolean'}, + fliph: {type: 'boolean'}, + aura1_square: {type: 'boolean'}, + aura2_square: {type: 'boolean'}, + + // bounded by screen size + left: {type: 'number', transform: 'screen'}, + top: {type: 'number', transform: 'screen'}, + width: {type: 'number', transform: 'screen'}, + height: {type: 'number', transform: 'screen'}, + + // 360 degrees + rotation: {type: 'degrees'}, + light_angle: {type: 'degrees'}, + light_losangle: {type: 'degrees'}, + + // distance + light_radius: {type: 'numberBlank'}, + light_dimradius: {type: 'numberBlank'}, + aura1_radius: {type: 'numberBlank'}, + aura2_radius: {type: 'numberBlank'}, + + // text or numbers + bar1_value: {type: 'text'}, + bar2_value: {type: 'text'}, + bar3_value: {type: 'text'}, + bar1_max: {type: 'text'}, + bar2_max: {type: 'text'}, + bar3_max: {type: 'text'}, + bar1: {type: 'text'}, + bar2: {type: 'text'}, + bar3: {type: 'text'}, + + // colors + aura1_color: {type: 'color'}, + aura2_color: {type: 'color'}, + tint_color: {type: 'color'}, + + // Text : special + name: {type: 'text'}, + statusmarkers: {type: 'status'}, + layer: {type: 'layer'}, + represents: {type: 'character_id'}, + bar1_link: {type: 'attribute'}, + bar2_link: {type: 'attribute'}, + bar3_link: {type: 'attribute'} + // controlledby: {type: 'player_id'} + }, + + regex = { + numberString: /^[+\-]?(0|[1-9][0-9]*)([.]+[0-9]*)?([eE][+\-]?[0-9]+)?$/, + stripSingleQuotes: /'([^']+(?='))'/g, + stripDoubleQuotes: /"([^"]+(?="))"/g, + layers: /^(?:gmlayer|objects|map|walls)$/, + statuses: /^(?:red|blue|green|brown|purple|pink|yellow|dead|skull|sleepy|half-heart|half-haze|interdiction|snail|lightning-helix|spanner|chained-heart|chemical-bolt|death-zone|drink-me|edge-crack|ninja-mask|stopwatch|fishing-net|overdrive|strong|fist|padlock|three-leaves|fluffy-wing|pummeled|tread|arrowed|aura|back-pain|black-flag|bleeding-eye|bolt-shield|broken-heart|cobweb|broken-shield|flying-flag|radioactive|trophy|broken-skull|frozen-orb|rolling-bomb|white-tower|grab|screaming|grenade|sentry-gun|all-for-one|angel-outfit|archery-target)$/, + colors: /^(transparent|(?:#?[0-9a-fA-F]{6}))$/ + }, + filters = { + hasArgument: function(a) { + return a.match(/.+\|/); + }, + isBoolean: function(a) { + return _.has(fields,a) && 'boolean' === fields[a].type; + }, + isTruthyArgument: function(a) { + return _.contains([1,'1','on','yes','true','sure','yup'],a); + } + }, + transforms = { + degrees: function(t){ + var n = parseFloat(t,10); + if(!_.isNaN(n)) { + n %= 360; + } + return n; + } + }, + 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(id) { + var who=getObj('player',id).get('_displayname').split(' ')[0]; + sendChat('', + '/w '+who+' ' ++'
    ' + +'
    ' + +'TokenMod v'+version + +'
    ' + +'
    ' + +'

    TokenMod provides an interface to setting almost all setable properties of a token.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!token-mod '+ch('<')+'--help|--config|--on|--off|--flip|--set'+ch('>')+' '+ch('<')+'parameter'+ch('>')+' '+ch('[')+ch('<')+'parameter'+ch('>')+' ...'+ch(']')+' ... '+ch('[')+'--ids '+ch('<')+'token_id'+ch('>')+' '+ch('[')+ch('<')+'token_id'+ch('>')+' ...'+ch(']')+ch(']')+'' + +'
    ' + +'

    This command takes a list of modifications and applies them to the selected tokens (or tokens specified with --ids by a GM or Player depending on configuration). Note that each --option can be specified multiple times and in any order.

    ' + +'

    Note: If you are using multiple '+ch('@')+ch('{')+'target'+ch('|')+'token_id'+ch('}')+' calls in a macro, and need to adjust fewer than the supplied number of token ids, simply select the same token several times. The duplicates will be removed.

    ' + +'
      ' + +'
    • ' + +''+ch('<')+'--help'+ch('>')+' '+ch('-')+' Displays this help.' + +'
    • ' + +'
    • ' + +''+ch('<')+'--config'+ch('>')+' '+ch('-')+' Sets Config options. ' + +'
    • ' + +'
    • ' + +''+ch('<')+'--on'+ch('>')+' '+ch('-')+' Turns on any of the specified parameters (See Booleans below).' + +'
    • ' + +'
    • ' + +''+ch('<')+'--off'+ch('>')+' '+ch('-')+' Turns off any of the specified parameters (See Booleans below).' + +'
    • ' + +'
    • ' + +''+ch('<')+'--flip'+ch('>')+' '+ch('-')+' Flips the value of any of the specified parameters (See Booleans below).' + +'
    • ' + +'
    • ' + +''+ch('<')+'--set'+ch('>')+' '+ch('-')+' Each parameter is treated as a key and value, divided by a | character. Sets the key to the value. If the value has spaces, you must enclose it '+ch("'")+' or '+ch('quot')+'. See below for specific value handling logic.' + +'
    • ' + +'
    • ' + +''+ch('<')+'--ids'+ch('>')+' '+ch('-')+' Each parameter is a Token ID, usually supplied with something like '+ch('@')+ch('{')+'target'+ch('|')+'Target 1'+ch('|')+'token_id'+ch('}')+'. ' + +'By default, only a GM can use this argument. You can enable players to use it as well with --config players-can-ids|on.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + + +'Configuration' + +'
    ' + +'

    --ids takes token ids to operate on, separated by spaces.

    ' + +'
    '
    +				+'!token-mod --ids -Jbz-mlHr1UXlfWnGaLh -JbjeTZycgyo0JqtFj-r -JbjYq5lqfXyPE89CJVs --on showname showplayers_name'
    +			+'
    ' + +'

    Usually, you will want to specify these with the @{target} syntax:

    ' + +'
    '
    +				+'!token-mod --ids @{target|1|token_id} @{target|2|token_id} @{target|3|token_id} --on showname showplayers_name'
    +			+'
    ' + + +'

    --config takes option value pairs, separated by | characters.

    ' + +'
    '
    +				+'!token-mod --config option|value option|value'
    +			+'
    ' + +'

    There is currently one configuration option:

    ' + + +'
    ' + +'
      ' + +'
    • ' + +'
      ' + +( state.TokenMod.playersCanUse_ids ? 'ON' : 'OFF' ) + +'
      ' + +'players-can-ids '+ch('-')+' Determines if players can use --ids. Specifying a value which is true allows players to use --ids. Omitting a value flips the current setting.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + + +'Booleans' + +'
    ' + +'

    The --on, --off and --flip options only work on properties of a token that are either true or false, checkboxes. Specified properties will only be changed once, priority is given to arguments to --on first, then --off and finally to --flip.

    ' + +'
    ' + +'
    '
    +				+'!token-mod --on showname light_hassight --off isdrawing --flip flipv fliph'
    +			+'
    ' + +'
    ' + +'
    ' + +'

    Available Boolean Properties:

    ' + +'
    showname
    ' + +'
    showplayers_name
    ' + +'
    showplayers_bar1
    ' + +'
    showplayers_bar2
    ' + +'
    showplayers_bar3
    ' + +'
    showplayers_aura1
    ' + +'
    showplayers_aura2
    ' + +'
    playersedit_name
    ' + +'
    playersedit_bar1
    ' + +'
    playersedit_bar2
    ' + +'
    playersedit_bar3
    ' + +'
    playersedit_aura1
    ' + +'
    playersedit_aura2
    ' + +'
    light_otherplayers
    ' + +'
    light_hassight
    ' + +'
    isdrawing
    ' + +'
    flipv
    ' + +'
    fliph
    ' + +'
    aura1_square
    ' + +'
    aura2_square
    ' + +'
    '+ch(' ')+'
    ' + +'

    Any of the booleans can be set with the --set command by passing a true or false as the value

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set showname|yes isdrawing|no'
    +				+'
    ' + +'
    ' + +'
    ' + +'
    ' + + +'Set Syntax Guide' + +'
    ' + +'

    --set takes key value pairs, separated by | characters.

    ' + +'
    '
    +				+'!token-mod --set key|value key|value key|value'
    +			+'
    ' + + +'

    Note: You can now use inline rolls wherever you like:

    ' + +'
    '
    +				+'!token-mod --set bar'+ch('[')+ch('[')+'1d3'+ch(']')+ch(']')+'_value|X statusmarkers|blue:'+ch('[')+ch('[')+'1d9'+ch(']')+ch(']')+'|green:'+ch('[')+ch('[')+'1d9'+ch(']')+ch(']')
    +			+'
    ' + + +'

    Note: You can now use + or - before any number to make an adjustment to the current value:

    ' + +'
    '
    +				+'!token-mod --set bar1_value|-3 statusmarkers|blue:+1|green:-1'
    +			+'
    ' + + +'

    There are several types of keys with special value formats:

    ' + + +'
    ' + +'Numbers' + +'

    Number values can be any floating point number (though most fields will drop the fractional part).

    ' + +'

    Available Numbers Properties:

    ' + +'
    left
    ' + +'
    top
    ' + +'
    width
    ' + +'
    height
    ' + +'
    '+ch(' ')+'
    ' + +'

    It'+ch("'")+'s probably a good idea not to set the location of a token off screen or the width or height to 0.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set top|0 left|0 width|50 height|50'
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Numbers or Blank' + +'

    Just like the Numbers fields, except you can set them to blank as well.

    ' + +'

    Available Numbers or Blank Properties:

    ' + +'
    light_radius
    ' + +'
    light_dimradius
    ' + +'
    aura1_radius
    ' + +'
    aura2_radius
    ' + +'
    '+ch(' ')+'
    ' + +'

    Here is setting a standard DnD 5e torch, turning off aura1 and setting aura2 to 30. Note that the | is still required for setting a blank value, such as aura1_radius below.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set light_radius|40 light_dimradius|20 aura1_radius| aura2_radius|30'
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Degrees' + +'

    Any positive or negative integer.

    ' + +'

    Available Degrees Properties:

    ' + +'
    rotation
    ' + +'
    light_angle
    ' + +'
    light_losangle
    ' + +'
    '+ch(' ')+'
    ' + +'

    Rotating a token by 180 degrees, and setting line of sight angle to 90 degrees.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set rotation|180 light_losangle|90'
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Color' + +'

    Colors can be any hex number color (with or without preceeding #), or the word transparent.

    ' + +'

    Available Color Properties:

    ' + +'
    tint_color
    ' + +'
    aura1_color
    ' + +'
    aura2_color
    ' + +'
    '+ch(' ')+'
    ' + +'

    Turning off the tint and setting aura1 to a reddish color.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set tint_color|transparent aura1_color|ff3366'
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Text' + +'

    These can be pretty much anything. If your value has spaces in it, you need to enclose it in '+ch("'")+' or '+ch('quot')+'.

    ' + +'

    Available Text Properties:

    ' + +'
    name
    ' + +'
    bar1_value
    ' + +'
    bar2_value
    ' + +'
    bar3_value
    ' + +'
    bar1_max
    ' + +'
    bar2_max
    ' + +'
    bar3_max
    ' + +'
    bar1
    ' + +'
    bar2
    ' + +'
    bar3
    ' + +'
    '+ch(' ')+'
    ' + +'

    Setting the name to Sir Thomas and bar1 to 23.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set name|'+ch('"')+'Sir Thomas'+ch('"')+' bar1_value|23'
    +				+'
    ' + +'
    ' + +'

    bar1, bar2 and bar3 are special. Any value set on them will be set in both the _value and _max fields for that bar. This is most useful for setting hit points.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set bar1|'+ch('[')+ch('[')+'3d6+8'+ch(']')+ch(']')
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Layer' + +'

    There is only one Layer property. It can be one of 4 values, listed below.

    ' + +'

    Available Layer Property:

    ' + +'
    layer
    ' + +'
    '+ch(' ')+'
    ' + +'

    Available Layer Values:

    ' + + +'
    gmlayer
    ' + +'
    objects
    ' + +'
    map
    ' + +'
    walls
    ' + + +'
    '+ch(' ')+'
    ' + +'

    Moving something to the gmlayer.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set layer|gmlayer'
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Status' + +'

    There is only one Status property. Status has a somewhat complicated syntax to support the most possible flexibility.

    ' + +'

    Available Status Property:

    ' + +'
    statusmarkers
    ' + +'
    '+ch(' ')+'
    ' + + +'

    Status is the only property that supports multiple values, all seperated by | as seen below.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|blue|red|green|padlock|broken-shield'
    +				+'
    ' + +'
    ' + + +'

    You can optionally preface each status with a + to remind you it is being added. This command is identical:

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|+blue|+red|+green|+padlock|+broken-shield'
    +				+'
    ' + +'
    ' + + +'

    Each value can be followed by a colon and a number between 0 and 9. (The number following the dead status is ignored because it is special.)

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|blue:0|red:3|green|padlock:2|broken-shield:7'
    +				+'
    ' + +'
    ' + + +'

    The numbers following a status can be prefaced with a + or -, which causes their value to be applyed to the current value. Here'+ch("'")+'s and example showing blue getting incremented by 2, and padlock getting decremented by 1. Values will be bounded between 0 and 9.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|blue:+2|padlock:-1'
    +				+'
    ' + +'
    ' + + +'

    By default, status markers will be added, retaining whichever status markers are already present. You can override this behavior by prefacing a value with a - to cause the status to be removed. Below will remove the blue and padlock status.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|-blue|-padlock'
    +				+'
    ' + +'
    ' + + +'

    Sometimes it is convenient to have a way to add a status if it is not there, but remove it if it is. This allows marking tokens with markers and clearing them with the same command. You can preface a status with ! to toggle it'+ch("'")+'s state on and off. Here is an example that will add or remove the Rook piece from a token.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|!white-tower'
    +				+'
    ' + +'
    ' + + +'

    Sometimes, you might want to clear all status marker as part of setting a new status marker. You can do this by prefacing a status marker with a =. Note that this affects all status markers before as well, so you will want to do this only on the first status marker. Below all status markers are removed and the dead marker is set. (If you want to remove all status markers, just specify the same marker twice with a = and then a -.)

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|=dead'
    +				+'
    ' + +'
    ' + + +'

    Available Status Markers:

    ' + +'
    red
    ' + +'
    blue
    ' + +'
    green
    ' + +'
    brown
    ' + +'
    purple
    ' + +'
    pink
    ' + +'
    yellow
    ' + +'
    dead
    ' + +'
    skull
    ' + +'
    sleepy
    ' + +'
    half-heart
    ' + +'
    half-haze
    ' + +'
    interdiction
    ' + +'
    snail
    ' + +'
    lightning-helix
    ' + +'
    spanner
    ' + +'
    chained-heart
    ' + +'
    chemical-bolt
    ' + +'
    death-zone
    ' + +'
    drink-me
    ' + +'
    edge-crack
    ' + +'
    ninja-mask
    ' + +'
    stopwatch
    ' + +'
    fishing-net
    ' + +'
    overdrive
    ' + +'
    strong
    ' + +'
    fist
    ' + +'
    padlock
    ' + +'
    three-leaves
    ' + +'
    fluffy-wing
    ' + +'
    pummeled
    ' + +'
    tread
    ' + +'
    arrowed
    ' + +'
    aura
    ' + +'
    back-pain
    ' + +'
    black-flag
    ' + +'
    bleeding-eye
    ' + +'
    bolt-shield
    ' + +'
    broken-heart
    ' + +'
    cobweb
    ' + +'
    broken-shield
    ' + +'
    flying-flag
    ' + +'
    radioactive
    ' + +'
    trophy
    ' + +'
    broken-skull
    ' + +'
    frozen-orb
    ' + +'
    rolling-bomb
    ' + +'
    white-tower
    ' + +'
    grab
    ' + +'
    screaming
    ' + +'
    grenade
    ' + +'
    sentry-gun
    ' + +'
    all-for-one
    ' + +'
    angel-outfit
    ' + +'
    archery-target
    ' + + +'
    '+ch(' ')+'
    ' + +'

    All of these operations can be combine in a single statusmarkers command.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set statusmarkers|blue:3|-dead|red:3'
    +				+'
    ' + +'
    ' + +'
    ' + + +'
    ' + +'Character ID' + +'

    You can use the '+ch('@')+ch('{')+ch('<')+'character name'+ch('>')+ch('|')+'character_id'+ch('}')+' syntax to specify a character_id directly or use the name of a character (quoted if it contains spaces) or just the shortest part of the name that is unique ('+ch("'")+'Sir Maximus Strongbow'+ch("'")+' could just be '+ch("'")+'max'+ch("'")+'.). Not case sensitive: Max = max = MaX = MAX

    ' + +'

    Available Character ID Properties:

    ' + +'
    represents
    ' + +'
    '+ch(' ')+'
    ' + +'

    Here is setting the represents to the character Bob.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set represents|'+ch('@')+ch('{')+'Bob'+ch('|')+'character_id'+ch('}')
    +				+'
    ' + +'
    ' + +'

    Note that setting the represents will clear the links for the bars, so you will probably want to set those again.

    ' + +'
    ' + + +'
    ' + +'Attribute Name' + +'

    These are resolved from the represented character id. If the token doesn'+ch("'")+'t represent a character, these will be ignored.

    ' + +'

    Available Attribute Name Properties:

    ' + +'
    bar1_link
    ' + +'
    bar2_link
    ' + +'
    bar3_link
    ' + +'
    '+ch(' ')+'
    ' + +'

    Here is setting the represents to the character Bob and setting bar1 to be the npc hitpoints attribute.

    ' + +'
    ' + +'
    '
    +					+'!token-mod --set represents|'+ch('@')+ch('{')+'Bob'+ch('|')+'character_id'+ch('}')+' bar1_link|npc_HP'
    +				+'
    ' + +'
    ' + +'
    ' + + + +'
    ' ++'
    ' + ); + }, + getRelativeChange = function(current,update) { + var cnum = current + && (_.isNumber(current) + ? current + : ( _.isString(current) + ? (current.match(regex.numberString) ? parseFloat(current,10) : NaN) + : NaN) + ), + unum = update + && (_.isNumber(update) + ? update + : ( _.isString(update) + ? (update.match(regex.numberString) ? parseFloat(update,10) : NaN) + : NaN) + ); + + if(!_.isNaN(unum) && !_.isUndefined(unum) ) { + if(!_.isNaN(cnum) && !_.isUndefined(cnum) ) { + switch(update[0]) { + case '+': + case '-': + return cnum+unum; + + default: + return unum; + } + } else { + return unum; + } + } + return update; + }, + parseArguments = function(a) { + var args=a.split(/\|/), + cmd=args.shift(), + retr={}, + t,t2; + + if(_.has(fields,cmd)) { + retr[cmd]=[]; + switch(fields[cmd].type) { + case 'boolean': + retr[cmd].push(filters.isTruthyArgument(args.shift())); + break; + + case 'text': + retr[cmd].push(args.shift().replace(regex.stripSingleQuotes,'$1').replace(regex.stripDoubleQuotes,'$1')); + break; + + case 'numberBlank': + retr[cmd].push( args[0].match(regex.numberString) ? args[0] : '' ); + if(_.isNaN(parseFloat(retr[cmd][0],10))) { + retr[cmd][0] = ''; + } + break; + + case 'number': + retr[cmd].push( args[0].match(regex.numberString) ? args[0] : undefined ); + if(_.isNaN(parseFloat(retr[cmd][0],10))) { + retr = undefined; + } + break; + + case 'degrees': + retr[cmd].push((_.contains(['-','+'],args[0][0]) ? args[0][0] : '') + Math.abs(transforms.degrees(args.shift()))); + break; + + case 'layer': + retr[cmd].push((args.shift().match(regex.layers)||[]).shift()); + if(0 === (retr[cmd][0]||'').length) { + retr = undefined; + } + break; + + case 'color': + retr[cmd].push((args.shift().match(regex.colors)||[]).shift()); + if(0 === (retr[cmd][0]||'').length) { + retr = undefined; + } else if('transparent' !== retr[cmd][0] && '#' !== retr[cmd][0].substr(0,1) ) { + retr[cmd][0]='#'+retr[cmd][0]; + } + break; + + case 'character_id': + t=getObj('character', args[0]); + if(t) { + retr[cmd].push(args[0]); + } else { + // try to find a character with this name + t2=findObjs({type: 'character',archived: false}); + t=_.chain([ args[0].replace(regex.stripSingleQuotes,'$1').replace(regex.stripDoubleQuotes,'$1') ]) + .map(function(n){ + var l=_.filter(t2,function(c){ + return c.get('name').toLowerCase() === n.toLowerCase(); + }); + return ( 1 === l.length ? l : _.filter(t2,function(c){ + return -1 !== c.get('name').toLowerCase().indexOf(n.toLowerCase()); + })); + }) + .flatten() + .value(); + if(1 === t.length) { + retr[cmd].push(t[0].id); + } else { + retr=undefined; + } + } + break; + + case 'attribute': + retr[cmd].push(args.shift().replace(regex.stripSingleQuotes,'$1').replace(regex.stripDoubleQuotes,'$1')); + break; + + case 'tofront': + break; + case 'toback' + break; + case 'randomdepth': + break; + + case 'player_id': + break; + + case 'status': + _.each(args, function(a) { + var s = a.split(/:/), + stat = s.shift(), + op = (_.contains(['-','+','=','!'],stat[0]) ? stat[0] : false), + numraw = s.shift() || '', + numop = (_.contains(['-','+'],numraw[0]) ? numraw[0] : false), + num = Math.max(0,Math.min(9,Math.abs(parseInt(numraw,10)))) || 0; + + stat = ( op ? stat.substring(1) : stat); + + if(stat.match(regex.statuses)) { + retr[cmd].push({ + status: stat, + number: num, + sign: numop, + operation: op || '+' + }); + } + }); + break; + + default: + retr=undefined; + break; + } + } + + return retr; + }, + expandMetaArguments = function(memo,a) { + var args=a.split(/\|/), + cmd=args.shift(); + switch(cmd) { + case 'bar1': + case 'bar2': + case 'bar3': + args=args.join('|'); + memo.push(cmd+'_value|'+args); + memo.push(cmd+'_max|'+args); + break; + default: + memo.push(a); + break; + } + return memo; + }, + + parseSetArguments = function(list,base) { + return _.chain(list) + .filter(filters.hasArgument) + .reduce(expandMetaArguments,[]) + .map(parseArguments) + .reject(_.isUndefined) + .reduce(function(memo,i){ + _.each(i,function(v,k){ + switch(k){ + case 'statusmarkers': + if(_.has(memo,k)) { + memo[k]=_.union(v,memo[k]); + } else { + memo[k]=v; + } + break; + default: + memo[k]=v; + break; + } + }); + return memo; + },base) + .value(); + }, + applyModListToToken = function(modlist, token) { + var mods={}, + delta, cid, + current=_.reduce(token.get('statusmarkers').split(/,/),function(memo,st){ + var parts=st.split(/@/); + if(parts[0].length) { + memo[parts[0]]=parseInt(parts[1],10)||0; + } + return memo; + },{}); + + _.each(modlist.on,function(f){ + mods[f]=true; + }); + _.each(modlist.off,function(f){ + mods[f]=false; + }); + _.each(modlist.flip,function(f){ + mods[f]=!token.get(f); + }); + _.each(modlist.set,function(f,k){ + switch(k) { + case 'statusmarkers': + _.each(f, function (sm){ + switch(sm.operation){ + case '!': + if(_.has(current,sm.status)){ + current = _.omit(current,sm.status); + } else { + current[sm.status] = Math.max(0,Math.min(9,getRelativeChange(current[sm.status], sm.sign+sm.number))); + } + break; + case '+': + current[sm.status] = Math.max(0,Math.min(9,getRelativeChange(current[sm.status], sm.sign+sm.number))); + break; + case '-': + current = _.omit(current,sm.status); + break; + case '=': + current = {}; + current[sm.status] = Math.max(0,Math.min(9,getRelativeChange(current[sm.status], sm.sign+sm.number))); + break; + } + }); + break; + + case 'represents': + mods[k]=f[0]; + mods.bar1_link=''; + mods.bar2_link=''; + mods.bar3_link=''; + break; + + case 'bar1_link': + case 'bar2_link': + case 'bar3_link': + cid=mods.represents || token.get('represents') || ''; + if('' !== cid) { + delta=findObjs({type: 'attribute', characterid: cid, name: f[0]})[0]; + if(delta) { + mods[k]=delta.id; + mods[k.split(/_/)[0]+'_value']=delta.get('current'); + mods[k.split(/_/)[0]+'_max']=delta.get('max'); + } + } + break; + + case 'left': + case 'top': + case 'width': + case 'height': + delta=getRelativeChange(token.get(k),f[0]); + if(_.isNumber(delta)) { + mods[k]=delta; + } + break; + + case 'rotation': + case 'light_angle': + case 'light_losangle': + delta=getRelativeChange(token.get(k),f[0]); + if(_.isNumber(delta)) { + mods[k]=(delta%360); + } + break; + + case 'light_radius': + case 'light_dimradius': + case 'aura1_radius': + case 'aura2_radius': + delta=getRelativeChange(token.get(k),f[0]); + if(_.isNumber(delta) || '' === delta) { + mods[k]=delta; + } + break; + + case 'bar1_value': + case 'bar2_value': + case 'bar3_value': + case 'bar1_max': + case 'bar2_max': + case 'bar3_max': + case 'name': + delta=getRelativeChange(token.get(k),f[0]); + if(_.isNumber(delta) || _.isString(delta)) { + mods[k]=delta; + } + break; + default: + mods[k]=f[0]; + break; + } + }); + mods.statusmarkers=_.map(current,function(v,k){ return ('dead' === k) ? (k) : (k+'@'+v);}).join(','); + token.set(mods); + }, + + handleConfig = function(config, id) { + var args, cmd, who=getObj('player',id).get('_displayname').split(' ')[0]; + + while(config.length) { + args=config.shift().split(/\|/); + cmd=args.shift(); + switch(cmd) { + case 'players-can-ids': + if(args.length) { + state.TokenMod.playersCanUse_ids = filters.isTruthyArgument(args.shift()); + } else { + state.TokenMod.playersCanUse_ids = !state.TokenMod.playersCanUse_ids; + } + sendChat('', '/w '+who+'
    ' + + ( state.TokenMod.playersCanUse_ids ? 'Players can now use --ids to specify targets to change.' : 'Players cannot use --ids.' ) + +'
    ' + ); + break; + default: + sendChat('', '/w '+who+'
    ' + +'Error: ' + +'No configuration setting for ['+cmd+']' + +'
    ' + ); + break; + } + } + }, + + handleInput = function(msg_orig) { + var msg = _.clone(msg_orig), + args, cmds, ids=[], + modlist={ + flip: [], + on: [], + off: [], + set: {} + }; + + if (msg.type !== "api") { + return; + } + + if(_.has(msg,'inlinerolls')){ + msg.content = _.chain(msg.inlinerolls) + .reduce(function(m,v,k){ + m['$[['+k+']]']=v.results.total || 0; + return m; + },{}) + .reduce(function(m,v,k){ + return m.replace(k,v); + },msg.content) + .value(); + } + + args = msg.content.split(/\s+--/); + switch(args.shift()) { + case '!token-mod': + + while(args.length) { + cmds=args.shift().match(/([^\s]+\|'[^']+'|[^\s]+\|"[^"]+"|[^\s]+)/g); + switch(cmds.shift()) { + case 'help': + showHelp(msg.playerid); + return; + + case 'config': + if(isGM(msg.playerid)) { + handleConfig(cmds,msg.playerid); + } + return; + + case 'flip': + modlist.flip=_.union(_.filter(cmds,filters.isBoolean),modlist.flip); + break; + + case 'on': + modlist.on=_.union(_.filter(cmds,filters.isBoolean),modlist.on); + break; + + case 'off': + modlist.off=_.union(_.filter(cmds,filters.isBoolean),modlist.off); + break; + + case 'set': + modlist.set=parseSetArguments(cmds,modlist.set); + break; + + case 'ids': + ids=_.union(cmds,ids); + break; + } + } + modlist.off=_.difference(modlist.off,modlist.on); + modlist.flip=_.difference(modlist.flip,modlist.on,modlist.off); + + if(isGM(msg.playerid) || state.TokenMod.playersCanUse_ids ) { + _.chain(ids) + .uniq() + .map(function(t){ + return getObj('graphic',t); + }) + .reject(_.isUndefined) + .each(function(t) { + applyModListToToken(modlist,t); + }); + } + + _.each(msg.selected,function (o) { + applyModListToToken(modlist,getObj(o._type,o._id)); + }); + break; + + } + + }, + checkInstall = function() { + if( ! _.has(state,'TokenMod') || state.TokenMod.version !== schemaVersion) { + state.TokenMod = { + version: schemaVersion, + playersCanUse_ids: false + }; + } + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + TokenMod.CheckInstall(); + TokenMod.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('TokenMod requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..e62961f4a5 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "TokenMod", + "version": "0.54", + "description": "An interface to adjusting properties of a token from a macro or the chat area.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.TokenMod": "read,write", + "attribute.characterid": "read", + "attribute.current": "read", + "attribute.id": "read", + "attribute.max": "read", + "attribute.name": "read", + "attribute.type": "read", + "character.archived": "read", + "character.name": "read", + "graphic.*": "read,write" + }, + "conflicts": [ + ] +} From 379fa6a9a0936c6db6116bf83b4e39fb56769807 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:11 -0600 Subject: [PATCH 29/42] Squashed 'TokenNameNumber/' content from commit 0d25edc git-subtree-dir: TokenNameNumber git-subtree-split: 0d25edcaf53c616e88cbcf34e6c2aec05b89ba92 --- TokenNameNumber.js | 115 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 19 ++++++++ 2 files changed, 134 insertions(+) create mode 100644 TokenNameNumber.js create mode 100644 package.json diff --git a/TokenNameNumber.js b/TokenNameNumber.js new file mode 100644 index 0000000000..ff023d7764 --- /dev/null +++ b/TokenNameNumber.js @@ -0,0 +1,115 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TokenNameNumber/TokenNameNumber.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var TokenNameNumber = TokenNameNumber || (function() { + 'use strict'; + + var version = 0.2, + schemaVersion = 0.1, + + checkInstall = function() { + if( ! _.has(state,'TokenNameNumber') || state.TokenNameNumber.version !== schemaVersion) { + state.TokenNameNumber = { + version: schemaVersion, + registry: { + } + }; + } + }, + + getMatchers = function(pageid,represents) { + var matchers = []; + if(_.has(state.TokenNameNumber.registry, pageid) + && _.has(state.TokenNameNumber.registry[pageid],represents) ) { + _.each(state.TokenNameNumber.registry[pageid][represents], function(regstr) { + matchers.push(new RegExp(regstr)); + }); + } + return matchers; + }, + addMatcher = function(pageid,represents,matcherRegExpStr) { + if( ! _.has(state.TokenNameNumber.registry, pageid) ) { + state.TokenNameNumber.registry[pageid] = {}; + } + if( ! _.has(state.TokenNameNumber.registry[pageid],represents) ) { + state.TokenNameNumber.registry[pageid][represents]=[matcherRegExpStr]; + } else { + state.TokenNameNumber.registry[pageid][represents].push(matcherRegExpStr); + } + }, + + setNumberFunction = function(id) { + var obj = getObj('graphic',id), + matchers = (obj && getMatchers(obj.get('pageid'), obj.get('represents'))) || [], + tokenName = (obj && obj.get('name')), + matcher, + renamer, + parts, + num; + + + if(obj && (tokenName.match( /%%NUMBERED%%/ ) || _.some(matchers,function(m) { return m.test(tokenName);}) ) ) { + if( 0 === matchers.length || !_.some(matchers,function(m) { return m.test(tokenName);}) ) { + matcher='^('+tokenName.replace(/%%NUMBERED%%/,')(\\d+)(')+')$'; + addMatcher(obj.get('pageid'), obj.get('represents'), matcher ); + } + if( !_.some(matchers,function(m) { + if(m.test(tokenName)) { + matcher=m; + return true; + } + return false; + }) ) { + matcher=new RegExp('^('+tokenName.replace(/%%NUMBERED%%/,')(\\d+)(')+')$'); + renamer=new RegExp('^('+tokenName.replace(/%%NUMBERED%%/,')(%%NUMBERED%%)(')+')$'); + } + renamer = renamer || matcher; + + num = (_.chain(findObjs({ + type: 'graphic', + subtype: 'token', + represents: obj.get('represents'), + pageid: obj.get('pageid') + })) + .filter(function(t){ + return matcher.test(t.get('name')); + }) + .reduce(function(memo,t){ + var c=parseInt(matcher.exec(t.get('name'))[2],10); + return Math.max(memo,c); + },0) + .value() ); + + parts=renamer.exec(tokenName); + obj.set({ + name: parts[1]+(++num)+parts[3] + }); + } + }, + + setNumberOnToken = function(obj) { + if( 'graphic' === obj.get('type') + && 'token' === obj.get('subtype') ) { + + setTimeout(_.bind(setNumberFunction,this,obj.id), 500); + } + }, + + registerEventHandlers = function() { + on('add:graphic', setNumberOnToken); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + TokenNameNumber.CheckInstall(); + TokenNameNumber.RegisterEventHandlers(); +}); + diff --git a/package.json b/package.json new file mode 100644 index 0000000000..0b643ca8e8 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "TokenNameNumber", + "version": "0.2", + "description": "Automatic Numbering of tokens with special placeholder.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "state.TokenNameNumber": "read,write", + "graphic.name", "read,write", + "graphic.pageid", "read", + "graphic.represents", "read", + "graphic.subtype", "read", + "graphic.type", "read" + }, + "conflicts": [ + ] +} From 87fc33dc5a56861b7b7fff0bb61e0e3033cd20ea Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:12 -0600 Subject: [PATCH 30/42] Squashed 'Torch/' content from commit f971715 git-subtree-dir: Torch git-subtree-split: f971715d2f9210d51d7f02dccbdb50b36200f7e8 --- Torch.js | 458 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 32 ++++ 2 files changed, 490 insertions(+) create mode 100644 Torch.js create mode 100644 package.json diff --git a/Torch.js b/Torch.js new file mode 100644 index 0000000000..d01ffd1526 --- /dev/null +++ b/Torch.js @@ -0,0 +1,458 @@ +// 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.7, + schemaVersion = 0.1, + flickerURL = 'https://s3.amazonaws.com/files.d20.io/images/4277467/iQYjFOsYC5JsuOPUCI9RGA/thumb.png?1401938659', + flickerPeriod = 400, + flickerDeltaLocation = 2, + flickerDeltaRadius = 0.1, + flickerInterval = false, + + 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 ' ++'
    ' + +'
    ' + +'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. Tokens for flicker lights that have been removed are stored on the GM layer in the upper left corner and can be removed if desired. They will be reused if a new flicker light is requested.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!torch ['+ch('<')+'Radius'+ch('>')+' ['+ch('<')+'Dim Start'+ch('>')+' ['+ch('<')+'All Players'+ch('>')+' ['+ch('<')+'Token ID'+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('}')+'.' + +'
    • ' + +'
    ' + +'
    ' + +'!snuff ['+ch('<')+'Token ID'+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('<')+'Radius'+ch('>')+' ['+ch('<')+'Dim Start'+ch('>')+' ['+ch('<')+'All Players'+ch('>')+' ['+ch('<')+'Token ID'+ch('>')+' ... ]]]]' + +'
    ' + +'

    Behaves identically to !torch, save that it creates a flickering light.

    ' + +'
    ' + +'!flicker-off ['+ch('<')+'Token ID'+ch('>')+' ... ]' + +'
    ' + +'

    Behaves identically to !snuff, save that it affects the flickering light.

    ' + +'
    ' + +'!daytime ['+ch('<')+'Token ID'+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('<')+'Token ID'+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('}')+'.' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + setFlicker = function(o,r,d,p) { + var found = _.findWhere(state.Torch.flickers, {parent: o.id}), + fobj; + + if( found ) { + fobj = getObj('graphic',found.id); + if(fobj) { + fobj.set({ + layer: 'objects', + showname: false, + aura1_radius: '', + showplayers_aura1: false, + light_radius: r, + light_dimradius: d, + light_otherplayers: p + }); + } else { + delete state.Torch.flickers[found.id]; + } + } + + if(!fobj) { + found = _.findWhere(state.Torch.flickers, {page: o.get('pageid'), active: false}); + while(!fobj && found ) { + fobj = getObj('graphic', found.id); + if(fobj) { + fobj.set({ + layer: 'objects', + showname: false, + aura1_radius: '', + showplayers_aura1: false, + light_radius: r, + light_dimradius: d, + light_otherplayers: p + }); + } else { + delete state.Torch.flickers[found.id]; + found = _.findWhere(state.Torch.flickers, {page: o.get('pageid'), active: false}); + } + } + } + + if(!fobj) { + // new flicker + fobj =fixNewObj(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: 'objects', + light_radius: r, + light_dimradius: d, + light_otherplayers: p + })); + } + 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 + }; + }, + + clearFlicker = function(fid) { + var f = getObj('graphic',fid); + if(f) { + f.set({ + aura1_radius: 1, + aura1_square: false, + aura1_color: '#ffbd00', + showplayers_aura1: false, + light_radius: '', + ligh_dimradius: '', + light_otherplayers: false, + showname: true, + top: 70, + left: 70, + layer: 'gmlayer' + }); + } + state.Torch.flickers[fid].active=false; + }, + + handleInput = function(msg) { + var args, radius, dim_radius, other_players, page, obj, objs=[]; + + if (msg.type !== "api") { + return; + } + + args = msg.content.split(" "); + switch(args[0]) { + case '!torch': + if('help' === args[1] || ( !_.has(msg,'selected') && args.length < 5)) { + showHelp(); + 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 ); + + if(isGM(msg.playerid)) { + _.chain(args) + .rest(4) + .uniq() + .map(function(t){ + return getObj('graphic',t); + }) + .reject(_.isUndefined) + .each(function(t) { + t.set({ + light_radius: radius, + light_dimradius: dim_radius, + light_otherplayers: other_players + }); + }); + } + + _.each(msg.selected,function (o) { + getObj(o._type,o._id).set({ + light_radius: radius, + light_dimradius: dim_radius, + light_otherplayers: other_players + }); + }); + break; + + case '!snuff': + if('help' === args[1] || ( !_.has(msg,'selected') && args.length < 2)) { + showHelp(); + return; + } + + if(isGM(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 + }); + }); + } + _.each(msg.selected,function (o) { + getObj(o._type,o._id).set({ + light_radius: '', + light_dimradius: '', + light_otherplayers: false + }); + }); + break; + + case '!daytime': + if('help' === args[1]) { + showHelp(); + return; + } + if(isGM(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('help' === args[1]) { + showHelp(); + return; + } + if(isGM(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 '!flicker-on': + if('help' === args[1] || ( !_.has(msg,'selected') && args.length < 5)) { + showHelp(); + 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 ); + + if(isGM(msg.playerid)) { + objs=_.chain(args) + .rest(4) + .uniq() + .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); + }); + + break; + + case '!flicker-off': + if('help' === args[1] || ( !_.has(msg,'selected') && args.length < 2)) { + showHelp(); + return; + } + + if(isGM(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; + + 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); + f.set({ + top: o.get('top')+dy, + left: o.get('left')+dx, + light_radius: fdata.light_radius+dr + }); + } + } + }); + + }, + + 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() { + if( ! _.has(state,'Torch') || state.Torch.version !== schemaVersion) { + log('Torch: Resetting state'); + /* 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'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + Torch.CheckInstall(); + Torch.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('Torch requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..1d7a9e2eb8 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "Torch", + "version": "0.7", + "description": "A simple script for giving lights to tokens and turning off and on dynamic lighting.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.Torch": "read,write", + "campaign.playerpageid": "read", + "campaign.playerspecificpages": "read", + "graphic": "create", + "graphic.aura1_color": "read,write", + "graphic.aura1_radius": "read,write", + "graphic.aura1_square": "read,write", + "graphic.layer": "read,write", + "graphic.left": "read,write", + "graphic.light_dimradius": "read,write", + "graphic.light_otherplayers": "read,write", + "graphic.light_radius": "read,write", + "graphic.pageid": "read", + "graphic.showname": "read,write", + "graphic.showplayers_aura1": "read,write", + "graphic.top": "read,write", + "page.name": "read", + "page.showlighting": "read,write" + }, + "conflicts": [ + ] +} From effd01702baf0b89408b57e11e267592c7a9fc7d Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:13 -0600 Subject: [PATCH 31/42] Squashed 'TurnMarker1/' content from commit a0beace git-subtree-dir: TurnMarker1 git-subtree-split: a0beace167776bddcd1c0649e851d714f4c9e43b --- TurnMarker.js | 738 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 37 +++ 2 files changed, 775 insertions(+) create mode 100644 TurnMarker.js create mode 100644 package.json diff --git a/TurnMarker.js b/TurnMarker.js new file mode 100644 index 0000000000..f8bcfe804a --- /dev/null +++ b/TurnMarker.js @@ -0,0 +1,738 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/TurnMarker1/TurnMarker.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +/* ############################################################### */ +/* TurnMarker */ +/* ############################################################### */ + +var TurnMarker = TurnMarker || { + version: 1.22, + schemaVersion: 1.16, + active: false, + threadSync: 1, + + CheckInstall: function() { + if( ! state.hasOwnProperty('TurnMarker') || state.TurnMarker.version != TurnMarker.schemaVersion) + { + /* Default Settings stored in the state. */ + state.TurnMarker = { + version: TurnMarker.version, + announceRounds: true, + announceTurnChange: true, + announcePlayerInTurnAnnounce: true, + announcePlayerInTurnAnnounceSize: '100%', + autoskipHidden: true, + tokenName: 'Round', + tokenURL: 'https://s3.amazonaws.com/files.d20.io/images/4095816/086YSl3v0Kz3SlDAu245Vg/thumb.png?1400535580', + playAnimations: false, + rotation: false, + animationSpeed: 5, + scale: 1.7, + aura1: { + pulse: false, + size: 5, + color: '#ff00ff' + }, + aura2: { + pulse: false, + size: 5, + color: '#00ff00' + } + } + } + if(Campaign().get('turnorder') =='') + { + Campaign().set('turnorder','[]'); + } + }, + + GetMarker: function(){ + var marker = findObjs({ + imgsrc: state.TurnMarker.tokenURL, + pageid: Campaign().get("playerpageid") + })[0]; + + if (marker === undefined) { + marker = createObj('graphic', { + name: state.TurnMarker.tokenName+' 0', + pageid: Campaign().get("playerpageid"), + layer: 'gmlayer', + imgsrc: state.TurnMarker.tokenURL, + left: 0, + top: 0, + height: 70, + width: 70, + bar2_value: 0, + showplayers_name: true, + showplayers_aura1: true, + showplayers_aura2: true + }); + marker=fixNewObject(marker); + } + if(!TurnOrder.HasTurn(marker.id)) + { + TurnOrder.AddTurn({ + id: marker.id, + pr: -1, + custom: "", + pageid: marker.get('pageid') + }); + } + return marker; + }, + + Step: function( sync ){ + if (!state.TurnMarker.playAnimations || sync != TurnMarker.threadSync) + { + return; + } + var marker=TurnMarker.GetMarker(); + if(TurnMarker.active === true) + { + var rotation=(marker.get('bar1_value')+state.TurnMarker.animationSpeed)%360; + marker.set('bar1_value', rotation ); + if(state.TurnMarker.rotation) + { + marker.set( 'rotation', rotation ); + } + if( state.TurnMarker.aura1.pulse ) + { + marker.set('aura1_radius', Math.abs(Math.sin(rotation * (Math.PI/180))) * state.TurnMarker.aura1.size ); + } + else + { + marker.set('aura1_radius',''); + } + if( state.TurnMarker.aura2.pulse ) + { + marker.set('aura2_radius', Math.abs(Math.cos(rotation * (Math.PI/180))) * state.TurnMarker.aura2.size ); + } + else + { + marker.set('aura2_radius',''); + } + setTimeout(_.bind(TurnMarker.Step,this,sync), 100); + } + }, + + Reset: function() { + TurnMarker.active=false; + TurnMarker.threadSync++; + + var marker = TurnMarker.GetMarker(); + + marker.set({ + layer: "gmlayer", + aura1_radius: '', + aura2_radius: '', + left: 35, + top: 35, + height: 70, + width: 70, + rotation: 0, + bar1_value: 0 + }); + }, + + Start: function() { + var marker = TurnMarker.GetMarker(); + + + if(state.TurnMarker.playAnimations && state.TurnMarker.aura1.pulse) + { + marker.set({ + aura1_radius: state.TurnMarker.aura1.size, + aura1_color: state.TurnMarker.aura1.color, + }); + } + if(state.TurnMarker.playAnimations && state.TurnMarker.aura2.pulse) + { + marker.set({ + aura2_radius: state.TurnMarker.aura2.size, + aura2_color: state.TurnMarker.aura2.color, + }); + } + TurnMarker.active=true; + TurnMarker.Step(TurnMarker.threadSync); + TurnMarker.TurnOrderChange(false); + }, + + HandleInput: function(tokens,who){ + switch (tokens[0]) + { + case 'reset': + var marker = TurnMarker.GetMarker(); + marker.set({ + name: state.TurnMarker.tokenName+' '+0, + bar2_value: 0 + }); + sendChat('','/w '+who+' Round count is reset to 0.'); + break; + + case 'toggle-announce': + state.TurnMarker.announceRounds=!state.TurnMarker.announceRounds; + sendChat('','/w '+who+' Announce Rounds is now '+(state.TurnMarker.announceRounds ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-announce-turn': + state.TurnMarker.announceTurnChange=!state.TurnMarker.announceTurnChange; + sendChat('','/w '+who+' Announce Turn Changes is now '+(state.TurnMarker.announceTurnChange ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-announce-player': + state.TurnMarker.announcePlayerInTurnAnnounce=!state.TurnMarker.announcePlayerInTurnAnnounce; + sendChat('','/w '+who+' Player Name in Announce is now '+(state.TurnMarker.announcePlayerInTurnAnnounce ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-skip-hidden': + state.TurnMarker.autoskipHidden=!state.TurnMarker.autoskipHidden; + sendChat('','/w '+who+' Auto-skip Hidden is now '+(state.TurnMarker.autoskipHidden ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-animations': + state.TurnMarker.playAnimations=!state.TurnMarker.playAnimations; + if(state.TurnMarker.playAnimations) + { + TurnMarker.Step(TurnMarker.threadSync); + } + else + { + var marker = TurnMarker.GetMarker(); + marker.set({ + aura1_radius: '', + aura2_radius: '', + }); + } + + sendChat('','/w '+who+' Animations are now '+(state.TurnMarker.playAnimations ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-rotate': + state.TurnMarker.rotation=!state.TurnMarker.rotation; + sendChat('','/w '+who+' Rotation is now '+(state.TurnMarker.rotation ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-aura-1': + state.TurnMarker.aura1.pulse=!state.TurnMarker.aura1.pulse; + sendChat('','/w '+who+' Aura 1 is now '+(state.TurnMarker.aura1.pulse ? 'ON':'OFF' )+'.'); + break; + + case 'toggle-aura-2': + state.TurnMarker.aura2.pulse=!state.TurnMarker.aura2.pulse; + sendChat('','/w '+who+' Aura 2 is now '+(state.TurnMarker.aura2.pulse ? 'ON':'OFF' )+'.'); + break; + + + default: + case 'help': + TurnMarker.Help(who); + break; + + } + }, + + Help: function(who){ + var marker = TurnMarker.GetMarker(); + var rounds =parseInt(marker.get('bar2_value')); + sendChat('', + '/w '+who+' ' ++'
    ' + +'
    ' + +'TurnMarker v'+TurnMarker.version + +'
    ' + +'Commands' + +'
    !tm' + +'
    ' + +'The following arguments may be supplied in order to change the configuration. All changes are persisted between script restarts.' + +'
      ' + +'
      '+rounds+'
      ' + +'
    • reset -- Sets the round counter back to 0.
    • ' + +'
      '+( state.TurnMarker.announceRounds ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-announce -- When on, each round will be announced to chat.
    • ' + +'
      '+( state.TurnMarker.announceTurnChange ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-announce-turn -- When on, the transition between visible turns will be announced.
    • ' + +'
      '+( state.TurnMarker.announcePlayerInTurnAnnounce ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-announce-player -- When on, the player(s) controlling the current turn are included in the turn announcement.
    • ' + +'
      '+( state.TurnMarker.autoskipHidden ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-skip-hidden -- When on, turn order will automatically be advanced past any hidden turns.
    • ' + +'
      '+( state.TurnMarker.playAnimations ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-animations -- Turns on turn marker animations. [Experimental!]
    • ' + +'
      '+( state.TurnMarker.rotation ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-rotate -- When on, the turn marker will rotate slowly clockwise. [Animation]
    • ' + +'
      '+( state.TurnMarker.aura1.pulse ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-aura-1 -- When on, aura 2 will pulse in and out. [Animation]
    • ' + +'
      '+( state.TurnMarker.aura2.pulse ? 'ON' : 'OFF' )+'
      ' + +'
    • toggle-aura-2 -- When on, aura 2 will pulse in and out. [Animation]
    • ' + +'
    ' + +'
    ' + +'
    ' + +'
    !eot' + +'
    ' + +'Players may execute this command to advance the initiative to the next turn. This only succeeds if the current token is one that the caller controls or if it is executed by a GM.' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + CheckForTokenMove: function(obj){ + if(TurnMarker.active) + { + var turnOrder = TurnOrder.Get(); + var current = _.first(turnOrder); + if( obj && current && current.id == obj.id) + { + TurnMarker.threadSync++; + + var marker = TurnMarker.GetMarker(); + marker.set({ + "top": obj.get("top"), + "left": obj.get("left") + }); + + setTimeout(_.bind(TurnMarker.Step,this,TurnMarker.threadSync), 300); + } + } + }, + + RequestTurnAdvancement: function(playerid){ + if(TurnMarker.active) + { + var turnOrder = TurnOrder.Get(); + var current = getObj('graphic',_.first(turnOrder).id); + var character = getObj('character',current.get('represents')); + if(isGM(playerid) + || ( undefined != current && + ( _.contains(current.get('controlledby').split(','),playerid) + || _.contains(current.get('controlledby').split(','),'all') ) + ) + || ( undefined != character && + ( _.contains(character.get('controlledby').split(','),playerid) + || _.contains(character.get('controlledby').split(','),'all') ) + ) + ) + { + TurnOrder.Next(); + TurnMarker.TurnOrderChange(true); + } + } + }, + + _AnnounceRound: function(round){ + if(state.TurnMarker.announceRounds) + { + sendChat( + '', + "/direct " + +"
    " + +"" + +"Round "+ round + +"" + +"
    " + ); + } + }, + _HandleMarkerTurn: function(){ + var marker = TurnMarker.GetMarker(); + var turnOrder = TurnOrder.Get(); + + if(turnOrder[0].id == marker.id) + { + var round=parseInt(marker.get('bar2_value'))+1; + marker.set({ + name: state.TurnMarker.tokenName+' '+round, + bar2_value: round + }); + TurnMarker._AnnounceRound(round); + TurnOrder.Next(); + } + }, + _HandleAnnounceTurnChange: function(){ + + if(state.TurnMarker.announceTurnChange ) + { + var marker = TurnMarker.GetMarker(); + var turnOrder = TurnOrder.Get(); + var currentToken = getObj("graphic", turnOrder[0].id); + if('gmlayer' == currentToken.get('layer')) + { + return; + } + var previousTurn=_.last(_.filter(turnOrder,function(element){ + var token=getObj("graphic", element.id); + return ((undefined != token) + && (token.get('layer')!='gmlayer') + && (element.id != marker.id)); + })); + + /* find previous token. */ + var previousToken = getObj("graphic", previousTurn.id); + var pImage=previousToken.get('imgsrc'); + var cImage=currentToken.get('imgsrc'); + var pRatio=previousToken.get('width')/previousToken.get('height'); + var cRatio=currentToken.get('width')/currentToken.get('height'); + + var pNameString="The Previous turn is done."; + if(previousToken && previousToken.get('showplayers_name')) + { + pNameString='' + +previousToken.get('name') + +'\'s turn is done.'; + } + + var cNameString='The next turn has begun!'; + if(currentToken && currentToken.get('showplayers_name')) + { + cNameString='' + +currentToken.get('name') + +', it\'s now your turn!'; + } + + + var PlayerAnnounceExtra=''; + if(state.TurnMarker.announcePlayerInTurnAnnounce) + { + var Char=currentToken.get('represents'); + if('' != Char) + { + var Char=getObj('character',Char); + if(Char && _.isFunction(Char.get)) + { + var Controllers=Char.get('controlledby').split(','); + _.each(Controllers,function(c){ + switch(c) + { + case 'all': + PlayerAnnounceExtra+='
    ' + +'All' + +'
    '; + break; + break; + default: + var player=getObj('player',c); + if(player) { + var PlayerColor=player.get('color'); + var PlayerName=player.get('displayname'); + PlayerAnnounceExtra+='
    ' + +PlayerName + +'
    '; + } + break; + } + }); + } + } + } + + var tokenSize=70; + sendChat( + '', + "/direct " + +"
    " + +'
    ' + +"" + + pNameString + +'
    ' + +'
    ' + +"" + + '' + +cNameString + + '' + +'
    ' + +'
    ' + +PlayerAnnounceExtra + +"
    " + ); + } + }, + + TurnOrderChange: function(FirstTurnChanged){ + var marker = TurnMarker.GetMarker(); + + if(Campaign().get('initiativepage') === false) + { + return; + } + + var turnOrder = TurnOrder.Get(); + + if (!turnOrder.length) return; + + var current = _.first(turnOrder); + + if(state.TurnMarker.playAnimations) + { + TurnMarker.threadSync++; + setTimeout(_.bind(TurnMarker.Step,this,TurnMarker.threadSync), 300); + } + + if (current.id == "-1") return; + + TurnMarker._HandleMarkerTurn(); + + if(state.TurnMarker.autoskipHidden) + { + TurnOrder.NextVisible(); + TurnMarker._HandleMarkerTurn(); + } + + turnOrder=TurnOrder.Get(); + + if(turnOrder[0].id == marker.id) + { + return; + } + + current = _.first(TurnOrder.Get()); + + var currentToken = getObj("graphic", turnOrder[0].id); + if(undefined != currentToken) + { + + if(FirstTurnChanged) + { + TurnMarker._HandleAnnounceTurnChange(); + } + + var size = Math.max(currentToken.get("height"),currentToken.get("width")) * state.TurnMarker.scale; + + if (marker.get("layer") == "gmlayer" && currentToken.get("layer") != "gmlayer") { + marker.set({ + "top": currentToken.get("top"), + "left": currentToken.get("left"), + "height": size, + "width": size + }); + setTimeout(function() { + marker.set({ + "layer": currentToken.get("layer") + }); + }, 500); + } else { + marker.set({ + "layer": currentToken.get("layer"), + "top": currentToken.get("top"), + "left": currentToken.get("left"), + "height": size, + "width": size + }); + } + toFront(currentToken); + } + }, + + DispatchInitiativePage: function(){ + if(Campaign().get('initiativepage') === false) + { + this.Reset(); + } + else + { + this.Start(); + } + }, + + RegisterEventHandlers: function(){ + on("change:campaign:initiativepage", function(obj, prev) { + TurnMarker.DispatchInitiativePage(); + }); + + on("change:campaign:turnorder", function(obj, prev) { + var prevOrder=JSON.parse(prev.turnorder); + var objOrder=JSON.parse(obj.get('turnorder')); + + if( undefined !=prevOrder + && undefined !=objOrder + && _.isArray(prevOrder) + && _.isArray(objOrder) + && 0 != prevOrder.length + && 0 != objOrder.length + && objOrder[0].id != prevOrder[0].id + ) + { + TurnMarker.TurnOrderChange(true); + } + }); + + on("change:graphic", function(obj,prev) { + TurnMarker.CheckForTokenMove(obj); + }); + + on("chat:message", function (msg) { + /* Exit if not an api command */ + if (msg.type != "api") return; + + /* clean up message bits. */ + msg.who = msg.who.replace(" (GM)", ""); + msg.content = msg.content.replace("(GM) ", ""); + + // get minimal player name (hopefully unique!) + var who=getObj('player',msg.playerid).get('_displayname').split(' ')[0]; + + var tokenized = msg.content.split(" "); + var command = tokenized[0]; + + switch(command) + { + case "!tm": + case "!turnmarker": + { + TurnMarker.HandleInput(_.rest(tokenized),who); + } + break; + + case "!eot": + { + TurnMarker.RequestTurnAdvancement(msg.playerid); + } + break; + } + }); + } + +}; + + + + + + +on("ready",function(){ + 'use strict'; + + var Has_IsGM=false; + try { + _.isFunction(isGM); + Has_IsGM=true; + } + catch (err) + { + log('--------------------------------------------------------------'); + log('TurnMarker requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } + + if( Has_IsGM ) + { + TurnMarker.CheckInstall(); + TurnMarker.RegisterEventHandlers(); + TurnMarker.DispatchInitiativePage(); + } +}); + +// Utility Function +var fixNewObject = fixNewObject || function(obj){ + var p = obj.changed._fbpath; + var new_p = p.replace(/([^\/]*\/){4}/, "/"); + obj.fbpath = new_p; + return obj; +} + + +var TurnOrder = TurnOrder || { + Get: function(){ + var to=Campaign().get("turnorder"); + to=(''===to ? '[]' : to); + return JSON.parse(to); + }, + Set: function(turnOrder){ + Campaign().set({turnorder: JSON.stringify(turnOrder)}); + }, + Next: function(){ + this.Set(TurnOrder.Get().rotate(1)); + if("undefined" !== typeof Mark && _.has(Mark,'Reset') && _.isFunction(Mark.Reset)) { + Mark.Reset(); + } + }, + NextVisible: function(){ + var turns=this.Get(); + var context={skip: 0}; + var found=_.find(turns,function(element){ + var token=getObj("graphic", element.id); + if( + (undefined !== token) + && (token.get('layer')!='gmlayer') + ) + { + return true; + } + else + { + this.skip++; + } + },context); + if(undefined !== found && context.skip>0) + { + this.Set(turns.rotate(context.skip)); + } + }, + HasTurn: function(id){ + return (_.filter(this.Get(),function(turn){ + return id == turn.id; + }).length != 0); + }, + AddTurn: function(entry){ + var turnorder = this.Get(); + turnorder.push(entry); + this.Set(turnorder); + } +} + +Object.defineProperty(Array.prototype, 'rotate', { + enumerable: false, + writable: true +}); + +Array.prototype.rotate = (function() { + var unshift = Array.prototype.unshift, + splice = Array.prototype.splice; + + return function(count) { + var len = this.length >>> 0, + count = count >> 0; + + unshift.apply(this, splice.call(this, count % len, len)); + return this; + }; +})(); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..669873d3fc --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "TurnMarker", + "version": "1.22", + "description": "Round counter and a moving marker that shows who's turn it is.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.TurnMarker": "read,write", + "campaign.initiativepage": "read", + "campaign.playerpageid": "read", + "campaign.turnorder": "read,write", + "character.controlledby": "read", + "graphic": "create", + "graphic.aura1_color": "read,write", + "graphic.aura1_radius": "read,write", + "graphic.aura2_color": "read,write", + "graphic.aura2_radius": "read,write", + "graphic.bar1_value": "read,write", + "graphic.bar2_value": "read,write", + "graphic.controlledby": "read", + "graphic.height": "read,write", + "graphic.layer": "read,write", + "graphic.left": "read,write", + "graphic.name": "read,write", + "graphic.pageid": "read", + "graphic.represents": "read", + "graphic.rotation": "read,write", + "graphic.showplayers_name": "read", + "graphic.top": "read,write", + "graphic.width": "read,write" + }, + "conflicts": [ + ] +} From 78e2a11bf78a43122c9d8eaf5466b4ef74156220 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:13 -0600 Subject: [PATCH 32/42] Squashed 'UsePower/' content from commit 3f07765 git-subtree-dir: UsePower git-subtree-split: 3f07765513e2a8a10bc088b4c620974bc1273e50 --- UsePower.js | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 +++ 2 files changed, 419 insertions(+) create mode 100644 UsePower.js create mode 100644 package.json diff --git a/UsePower.js b/UsePower.js new file mode 100644 index 0000000000..a31fc9b44d --- /dev/null +++ b/UsePower.js @@ -0,0 +1,399 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/UsePower/UsePower.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var UsePower = UsePower || (function() { + 'use strict'; + + var version = 0.31, + schemaVersion = 0.1, + + 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 ''; + }, + + capitalize = function(s) { + return s.charAt(0).toUpperCase() + s.slice(1); + }, + + showHelp = function() { + sendChat('', + '/w gm ' ++'
    ' + +'
    ' + +'UsePower v'+version + +'
    ' + +'
    ' + +'

    UsePower provides a way to instrument and track daily and encounter powers. It is intended for D&D 4E, but could be used for any system that requires the capability to flag abilities as used and reset them. Using an instrumented ability will mark it used and remove it as a token action. (Caveat: it will disappear the next time the owner deselects the token.)

    ' + +'

    Daily powers are restored to token actions after !long-rest is executed. Encounter abilities are restored to token actions after !short-rest or !long-rest is executed. Activating a used power will whisper to the player and GM that the power was already used.

    ' + +'
    ' + +'Commands' + +'
    ' + +'!add-use-power --'+ch('<')+'Character Name'+ch('>')+' [--[ encounter | daily ] '+ch('<')+'number'+ch('>')+' ['+ch('<')+'number'+ch('>')+' ...] ...]' + +'
    ' + +'

    For all character names, case is ignored and you may use partial 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 --max.

    ' + +'

    Omitting any --encounter and --daily parameters will cause a list of the character'+ch("'")+'s powers with the expected index numbers (Caveat: These numbers will change if you add powers to the character. You should look them up again before instrumenting if you have changed the list of powers.)

    ' + +'
      ' + +'
    • ' + +'--'+ch('<')+'Character Name'+ch('>')+' '+ch('-')+' This is the name of the character to list or instrument powers for.' + +'
    • ' + +'
    • ' + +'--'+ch('<')+'[ encounter | daily ]'+ch('>')+' '+ch('<')+'number'+ch('>')+' ['+ch('<')+'number'+ch('>')+' ...] '+ch('-')+' This specifies a list of abilities to instrument as either encounter or daily powers. You can specify as many powers as you like by number after these arguments. Numbers that do not index abilities or values that are not numbers are ignored. Duplicates are ignored. If you specify the same number to both an --encounter and a --daily parameter, it will be reported as an error. Powers that are already instrumented will be changed (So, if an ability is already instrumented as an encounter power and you specify it as a daily power, it will be changed to a daily power.).' + +'
    • ' + +'
    ' + +'
    ' + +'
    ' + +'
    ' + +'!short-rest' + +'
    ' + +'

    This command restores all expended encounter powers to token macros.

    ' + +'
    ' + +'
    ' + +'
    ' + +'!long-rest' + +'
    ' + +'

    This command restores all expended encounter and daily powers to token macros.

    ' + +'
    ' + +'
    ' + +'
    ' + +'!use-power '+ch('<')+'Type'+ch('>')+' '+ch('<')+'Ability ID'+ch('>')+'' + +'
    ' + +'

    This command requires 2 parameters. It is usually added by the instrumenting code. If you copy it from one ability to another, it will be updated with the correct Ability ID on save. Duplicating an existing character will also cause the new character'+ch("'")+'s abilities to be corrected. All abilities are validated and updated on restart of the API.

    ' + +'
    ' + +'
    ' ++'
    ' + ); + }, + + instrumentPower = function (type, power) { + var action=power.object.get('action'), + match=action.match(/!use-power\s+\S+\s+\S+/); + + if( match ) { + action = action.replace(/!use-power\s+\S+\s+\S+/,'!use-power '+type+' '+power.object.id); + } else { + action='!use-power '+type+' '+power.object.id+'\n'+action; + } + power.object.set({action: action}); + }, + validateAndRepairAbility = function(obj) { + var action=obj.get('action'), + match=action.match(/!use-power\s+(\S+)\s+(\S+)/); + if(match && match[2] && match[2] !== obj.id) { + action = action.replace(/!use-power\s+\S+\s+\S+/,'!use-power '+match[1]+' '+obj.id); + obj.set({action: action}); + } + }, + + handleInput = function(msg) { + var args, + who, + obj, + chars, + match, + notice, + abilities, + data, + dup, + cmds; + + if (msg.type !== "api") { + return; + } + + who=getObj('player',msg.playerid).get('_displayname').split(' ')[0]; + args = msg.content.split(" "); + switch(args[0]) { + + case '!short-rest': + if(!isGM(msg.playerid)) { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Only the GM can initiate a short rest.' + +'
    ' + ); + } else { + _.chain(state.UsePower.usedPowers.encounter) + .uniq() + .map(function(id){ + return getObj('ability',id); + }) + .reject(_.isUndefined) + .each(function(a){ + a.set({ + istokenaction: true + }); + }); + state.UsePower.usedPowers.encounter=[]; + } + break; + + case '!long-rest': + if(!isGM(msg.playerid)) { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Only the GM can initiate a long rest.' + +'
    ' + ); + } + else + { + _.chain(_.union(state.UsePower.usedPowers.encounter,state.UsePower.usedPowers.daily)) + .uniq() + .map(function(id){ + return getObj('ability',id); + }) + .reject(_.isUndefined) + .each(function(a){ + a.set({ + istokenaction: true + }); + }); + state.UsePower.usedPowers.encounter=[]; + state.UsePower.usedPowers.daily=[]; + } + break; + + case '!use-power': + if( 3 !== args.length ) { + showHelp(); + return; + } + if(_.contains(['encounter','daily'],args[1])) { + obj = getObj('ability',args[2]); + if(obj) { + obj.set({ + istokenaction: false + }); + if(_.contains(state.UsePower.usedPowers[args[1]],args[2])) { + notice ='
    ' + +'Error: ' + +capitalize(args[1])+' Power '+'['+obj.get('name')+'] has already been used.' + +'
    '; + if(!isGM(msg.playerid)) { + sendChat('','/w gm '+notice); + } + sendChat('','/w '+ who+' '+notice); + + } else { + state.UsePower.usedPowers[args[1]].push(args[2]); + } + return; + } + + } else { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Only durations of "encounter" and "daily" are supported. Do not know what to do with ['+args[1]+'].' + +'
    ' + ); + return; + } + break; + + case '!add-use-power': + if(!isGM(msg.playerid)) { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Only the GM can instrument abilites for user-power.' + +'
    ' + ); + } + else + { + args = _.rest(msg.content.split(" --")); + if(args.length) { + chars=findObjs({type: 'character',archived: false}); + match=_.chain([args[0]]) + .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()); + })); + }) + .flatten() + .value(); + + if(1 !== match.length) { + if(match.length) { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Character ['+args[0]+'] is ambiguous and matches '+match.length+' names: ' + +_.map(match,function(e){ + return e.get('name'); + }).join(', ') + +'' + +'
    ' + ); + } else { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Character ['+args[0]+'] does not match any names.' + +'
    ' + ); + } + } + else + { + match=match[0]; + abilities=findObjs({type: 'ability', characterid: match.id}); + data=_.chain(abilities) + .sort(function(o) { + return o.get('name').toLowerCase(); + }) + .map(function(o,idx) { + var action=o.get('action'), + match=action.match(/!use-power\s+(\S+)\s+\S+/); + + return { + name: o.get('name'), + current: (match && match[1]), + index: ++idx, + object: o + }; + },0) + .value(); + + if(1 === args.length) { + sendChat('','/w '+ who+' ' + +'
    ' + +'
    Available Powers:
    ' + +'
      ' + +_.reduce(data,function(context, o) { + return context+'
    1. '+o.name+(o.current ? ' ['+o.current+']' : '')+'
    2. '; + },'') + +'
    ' + +'
    ' + ); + } + else + { + cmds=_.chain(args) + .rest() + .map(function(c){ + var work = c.split(/\s+/), + cmd = work[0].toLowerCase(), + powers = _.chain(work) + .rest() + .map(function(p) { + return (parseInt(p,10) - 1); + }) + .filter(function(v) { + return !!v && (v)' + +'Warning: ' + +'Ignoring instrumenting type ['+o.type+']. Only supported types: '+types.join(', ')+'' + +'' + ); + return false; + }) + .reduce(function(context,o) { + context[o.type]=_.uniq(_.union(context[o.type],o.which)); + return context; + },{encounter:[], daily:[]}) + .value(); + + dup=_.intersection(cmds.encounter, cmds.daily); + if(dup.length) { + sendChat('','/w '+ who+' ' + +'
    ' + +'Error: ' + +'Powers cannot be both encounter and daily. Please specify each power only for one type. Duplicates: '+dup.join(', ')+'' + +'
    ' + ); + } else { + _.each(cmds.encounter, function(e) { + instrumentPower('encounter',data[e]); + }); + _.each(cmds.daily, function(e) { + instrumentPower('daily',data[e]); + }); + } + } + } + } else { + showHelp(); + } + } + break; + } + + }, + checkInstall = function() { + if( ! _.has(state,'UsePower') || state.UsePower.version !== schemaVersion) + { + state.UsePower = { + version: schemaVersion, + usedPowers: { + encounter: [], + daily: [] + } + }; + } + _.each(findObjs({type:'ability'}), validateAndRepairAbility); + }, + + registerEventHandlers = function() { + on('chat:message', handleInput); + on('add:ability', validateAndRepairAbility); + on('change:ability:action', validateAndRepairAbility); + }; + + return { + RegisterEventHandlers: registerEventHandlers, + CheckInstall: checkInstall + }; +}()); + +on("ready",function(){ + 'use strict'; + + if("undefined" !== typeof isGM && _.isFunction(isGM)) { + UsePower.CheckInstall(); + UsePower.RegisterEventHandlers(); + } else { + log('--------------------------------------------------------------'); + log('UsePower requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625'); + log('--------------------------------------------------------------'); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..c8abc183bd --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "UsePower", + "version": "0.31", + "description": "A script for instrumenting and tracking the use of encounter and daily powers.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.UsePower": "read,write", + "ability.action": "read,write", + "ability.istokenaction": "write", + "ability.name": "read", + "character.name": "read", + "player.displayname": "read" + }, + "conflicts": [ + ] +} From fbe3441bf4b79c7a1030c15a90165a8359afe497 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:14 -0600 Subject: [PATCH 33/42] Squashed 'Walls/' content from commit 0311820 git-subtree-dir: Walls git-subtree-split: 0311820ef7cbe400a6b5d8405922caf24e922ffc --- Walls.js | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 22 +++++ 2 files changed, 250 insertions(+) create mode 100644 Walls.js create mode 100644 package.json diff --git a/Walls.js b/Walls.js new file mode 100644 index 0000000000..16d6a456a5 --- /dev/null +++ b/Walls.js @@ -0,0 +1,228 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/Walls/Walls.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var Walls = Walls || { + version: 0.1, + schemaVersion: 0.2, + + CheckInstall: function() { + if( ! _.has(state,'Walls') || state.Walls.schemaVersion != Walls.schemaVersion) + { + /* Default Settings stored in the state. */ + state.Walls = { + version: Walls.schemaVersion, + work: {} + } + } + Walls._ResetWork(); + }, + _ResetWork: function() { + state.Walls.work={ + accumulating: {}, + mapGraphic: { + id: undefined, + x: 0.0, + y: 0.0, + width: 1.0, + height: 1.0 + }, + scale: { + x: 1.0, + y: 1.0 + }, + completedPaths: [], + workPath: [] + }; + }, + HandleInput: function(tokens,msg) { + switch(tokens[0]) + { + case 'begin': + Walls._ResetWork(); + if(_.has(msg,'selected') && 1==msg.selected.length && 'graphic'== msg.selected[0]._type) + { + var map=getObj('graphic',msg.selected[0]._id); + if(undefined != map) + { + var left=map.get('left'); + var top =map.get('top'); + var width=map.get('width'); + var height=map.get('height'); + + state.Walls.work.mapGraphic.id=map.id; + state.Walls.work.mapGraphic.x=Math.round((left-(width/2))*100)/100; + state.Walls.work.mapGraphic.y=Math.round((top-(height/2))*100)/100; + state.Walls.work.mapGraphic.height=height; + state.Walls.work.mapGraphic.width=width; + } + } + else + { + log('Walls: Warning - select exactly one graphic on the map layer.'); + } + break; + + case 'end': + if(state.Walls.work.workPath.length) + { + state.Walls.work.completedPaths.push(state.Walls.work.workPath); + state.Walls.work.workPath=[]; + } + if(undefined != state.Walls.work.mapGraphic.id) + { + var map=getObj('graphic',state.Walls.work.mapGraphic.id); + if(undefined != map) + { + _.each(state.Walls.work.completedPaths,function(p){ + // find bounding box + var lowX = 1000000.0, + centerX = 0, + highX = 0, + width = 0, + lowY = 1000000.0, + centerY = 0, + highY = 0, + height = 0; + + _.each(p,function(elem){ + switch(elem[0]) + { + case 'M': + lowX = _.min([lowX,elem[1]]); + highX = _.max([highX,elem[1]]); + lowY = _.min([lowY,elem[2]]); + highY = _.max([highY,elem[2]]); + break; + + case 'C': + lowX = _.min([lowX,elem[5]]); + highX = _.max([highX,elem[5]]); + lowY = _.min([lowY,elem[6]]); + highY = _.max([highY,elem[6]]); + break; + + default: + break; + } + }); + width = ( highX - lowX ); + height = ( highY - lowY ); + centerX = lowX + ( width / 2 ) + state.Walls.work.mapGraphic.x; + centerY = lowY + ( height / 2 ) + state.Walls.work.mapGraphic.y; + + var newP = [] + // re-bias points + _.each(p,function(elem){ + switch(elem[0]) + { + case 'M': + newP.push(['M',elem[1]-lowX,elem[2]-lowY]); + break; + case 'C': + newP.push(['C',elem[1]-lowX,elem[2]-lowY,elem[3]-lowX,elem[4]-lowY,elem[5]-lowX,elem[6]-lowY]); + break; + default: + break; + } + }); + + var pathstring=JSON.stringify(newP); + + var path=createObj('path',{ + pageid: map.get('pageid'), + stroke: '#ff0000', + left: centerX, + top: centerY, + width: width, + height: height, + stroke_width: 5, + layer: 'walls', + path: pathstring + }); + + path=fixNewObject(path); + }); + } + sendChat('','/w gm Walls finished.'); + } + break; + + case 'viewbox': + state.Walls.work.scale.x=state.Walls.work.mapGraphic.width/tokens[1]; + state.Walls.work.scale.y=state.Walls.work.mapGraphic.height/tokens[2]; + break; + + case 'moveto': + if(state.Walls.work.workPath.length) + { + state.Walls.work.completedPaths.push(state.Walls.work.workPath); + state.Walls.work.workPath=[]; + } + state.Walls.work.workPath.push(['M',(tokens[1]*state.Walls.work.scale.x),(tokens[2]*state.Walls.work.scale.y)]); + break; + + case 'curveto': + state.Walls.work.workPath.push(['C', + (tokens[1]*state.Walls.work.scale.x), (tokens[2]*state.Walls.work.scale.y), + (tokens[3]*state.Walls.work.scale.x), (tokens[4]*state.Walls.work.scale.y), + (tokens[5]*state.Walls.work.scale.x), (tokens[6]*state.Walls.work.scale.y) + ]); + break; + } + }, + RegisterEventHandlers: function(){ + on("chat:message", function (msg) { + /* Exit if not an api command */ + if (msg.type != "api") return; + + // get minimal player name (hopefully unique!) + var who=getObj('player',msg.playerid).get('_displayname').split(' ')[0]; + + var tokenized = msg.content.split(" "); + var command = tokenized[0]; + + switch(command) + { + case "!walls": + if(isGM(msg.playerid)) + { + Walls.HandleInput(_.rest(tokenized),msg); + } + break; + } + }); + } + +}; + + +on("ready",function(){ + var Has_IsGM=false; + try { + _.isFunction(isGM); + Has_IsGM=true; + } + catch (err) + { + log('--------------------------------------------------------------'); + log('Walls requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625') + log('--------------------------------------------------------------'); + } + + if( Has_IsGM ) + { + Walls.CheckInstall(); + Walls.RegisterEventHandlers(); + } +}); + +// Utility Function +var fixNewObject = fixNewObject || function(obj){ + var p = obj.changed._fbpath; + var new_p = p.replace(/([^\/]*\/){4}/, "/"); + obj.fbpath = new_p; + return obj; +}; + diff --git a/package.json b/package.json new file mode 100644 index 0000000000..3bd094e7f1 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "Walls", + "version": "0.1", + "description": "Builds dynamic lighting walls with an exported SVG path file.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.Walls": "read,write", + "graphic.height": "read", + "graphic.left": "read", + "graphic.pageid": "read", + "graphic.top": "read", + "graphic.width": "read", + "path": "create", + "player.displayname": "read" + }, + "conflicts": [ + ] +} From 72cb95bf3e04f04fd0b4e1430c225fdc3542ff44 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Sun, 1 Feb 2015 15:39:15 -0600 Subject: [PATCH 34/42] Squashed 'WeightedDice/' content from commit 09d379f git-subtree-dir: WeightedDice git-subtree-split: 09d379f7fdef480a631479f874ac13e687af4971 --- WeightedDice.js | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 19 +++++++++ 2 files changed, 128 insertions(+) create mode 100644 WeightedDice.js create mode 100644 package.json diff --git a/WeightedDice.js b/WeightedDice.js new file mode 100644 index 0000000000..8d136474cc --- /dev/null +++ b/WeightedDice.js @@ -0,0 +1,109 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/WeightedDice/WeightedDice.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var WeightedDice = WeightedDice || { + version: 0.2, + schemaVersion: 0.1, + + CheckInstall: function() { + if( ! _.has(state,'WeightedDice') || state.WeightedDice.schemaVersion != WeightedDice.schemaVersion) + { + /* Default Settings stored in the state. */ + state.WeightedDice = { + version: WeightedDice.schemaVersion + } + } + }, + + HandleInput: function(tokens,msg) { + + var sides = parseInt(tokens[0]); + var minroll = parseInt(tokens[1]); + + if( + tokens.length <2 + || 2 != tokens.length + || _.isNull(sides) + || !_.isNumber(sides) + || _.isNull(minroll) + || !_.isNumber(minroll) + || sides < minroll + ) + { + sendChat('','/w gm Usage: !weighted-die [number of sides] [minimum roll number]'); + return; + } + + var tableName='d'+sides+'min'+minroll; + // see if it's already defined + var tables=findObjs({type: 'rollabletable', name: tableName}); + if(tables.length) + { + sendChat('','/w gm Table '+tableName+' already exists.'); + } + else + { + var newTable=fixNewObject(createObj('rollabletable',{name: tableName})); + _.each(_.range(minroll,(sides+1)), function(r){ + var weight = ( (r == minroll) ? minroll : 1); + var newTableItem=fixNewObject(createObj('tableitem',{ + _rollabletableid: newTable.id, + name: r, + weight: weight + })); + }); + sendChat('','/w gm Table '+tableName+' created.'); + } + }, + + RegisterEventHandlers: function(){ + on("chat:message", function (msg) { + /* Exit if not an api command */ + if (msg.type != "api") return; + + + var tokenized = msg.content.split(" "); + var command = tokenized[0]; + + switch(command) + { + case "!weighted-die": + if(isGM(msg.playerid)) + { + WeightedDice.HandleInput(_.rest(tokenized),msg); + } + break; + } + }); + } +}; + +on("ready",function(){ + var Has_IsGM=false; + try { + _.isFunction(isGM); + Has_IsGM=true; + } + catch (err) + { + log('--------------------------------------------------------------'); + log('WeightedDice requires the isGM module to work.'); + log('isGM GIST: https://gist.github.com/shdwjk/8d5bb062abab18463625') + log('--------------------------------------------------------------'); + } + + if( Has_IsGM ) + { + WeightedDice.CheckInstall(); + WeightedDice.RegisterEventHandlers(); + } +}); + +// Utility Function +var fixNewObject = fixNewObject || function(obj){ + var p = obj.changed._fbpath; + var new_p = p.replace(/([^\/]*\/){4}/, "/"); + obj.fbpath = new_p; + return obj; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000000..5c31ebf814 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "WeightedDice", + "version": "0.2", + "description": "Automated creation of rollable tables where a minimum value is weighted with values it replaces. (example d6min4 possible values: 4 4 4 4 5 6)", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.WeightedDice": "read,write", + "rollabletable": "create", + "rollabletable.name": "read", + "rollabletable.type": "read", + "tableitem": "create" + }, + "conflicts": [ + ] +} From 17fd48964050f4879b678744c9584b1176a24499 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:01 -0600 Subject: [PATCH 35/42] Squashed 'IsGM/' changes from 45dfc28..1c16109 1c16109 prod-IsGM: Updated prod version of IsGM at version 0.7. git-subtree-dir: IsGM git-subtree-split: 1c161097c98eadc106d82c061f5762a4f3db4613 --- IsGM.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 IsGM.js diff --git a/IsGM.js b/IsGM.js new file mode 100644 index 0000000000..51c27d661a --- /dev/null +++ b/IsGM.js @@ -0,0 +1,98 @@ +// Github: https://github.com/shdwjk/Roll20API/blob/master/IsGM/IsGM.js +// By: The Aaron, Arcane Scriptomancer +// Contact: https://app.roll20.net/users/104025/the-aaron + +var IsGMModule = IsGMModule || (function() { + 'use strict'; + + var version = 0.7, + schemaVersion = 0.6, + active = true, + reset_password = "swordfish", + hasPlayerIsGM = ("undefined" !== typeof playerIsGM && _.isFunction(playerIsGM)), + + checkInstall = function() { + var players = findObjs({_type:"player"}); + + if( ! _.has(state,'IsGM') || ! _.has(state.IsGM,'version') || state.IsGM.version !== schemaVersion ) { + state.IsGM={ + version: schemaVersion, + gms: [], + players: [], + unknown: [] + }; + } + state.IsGM.unknown=_.difference( + _.pluck(players,'id'), + state.IsGM.gms, + state.IsGM.players + ); + active = (state.IsGM.unknown.length>0); + }, + + isGM = (hasPlayerIsGM ? playerIsGM : function(id) { + return _.contains(state.IsGM.gms,id); + }), + + handleMessages = function(msg) { + var player, tokenized, who; + + if(msg.type !== "api") { + if(active && msg.playerid !== 'API') { + if(_.contains(state.IsGM.unknown, msg.playerid)) { + player=getObj('player',msg.playerid); + + if("" === player.get('speakingas') || 'player|'+msg.playerid === player.get('speakingas')) { + if(msg.who === player.get('_displayname')) { + state.IsGM.players.push(msg.playerid); + } else { + state.IsGM.gms.push(msg.playerid); + sendChat('IsGM','/w gm '+player.get('_displayname')+' is now flagged as a GM.'); + } + state.IsGM.unknown=_.without(state.IsGM.unknown,msg.playerid); + active = (state.IsGM.unknown.length>0); + } + } + } + } else { + tokenized = msg.content.split(/\s+/); + + switch(tokenized[0]) { + case '!reset-isgm': + if(isGM(msg.playerid) || (tokenized.length>1 && tokenized[1] === reset_password)) { + delete state.IsGM; + checkInstall(); + sendChat('IsGM','/w gm IsGM data reset.'); + } else { + who=getObj('player',msg.playerid).get('_displayname').split(' ')[0]; + sendChat('IsGM','/w '+who+' ('+who+')Only GMs may reset the IsGM data.' + +'If you are a GM you can reset by specifying the reset password from' + +'the top of the IsGM script as an argument to !reset-isgm'); + } + break; + } + } + }, + + registerEventHandlers = function() { + if(! hasPlayerIsGM ) { + on('chat:message',handleMessages); + } + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers, + IsGM: isGM + }; +}()); + +on('ready',function() { + 'use strict'; + + IsGMModule.CheckInstall(); + IsGMModule.RegisterEventHandlers(); +}); + +var isGM = isGM || IsGMModule.IsGM; + diff --git a/package.json b/package.json index 91df07839a..564352780c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "IsGMModule", - "version": "0.6", + "version": "0.7", "description": "Adds a function, isGM(id), which returns true for gms and false for players. GM database is built in the state object automatically as players and gms send chat messages.", "authors": "The Aaron", "roll20userid": "104025", From 9efa9a3e74fa04fb345eb5b9603464208d9b189e Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:22 -0600 Subject: [PATCH 36/42] Squashed 'ManualAttribute/' changes from 5c89036..d1d0693 d1d0693 prod-ManualAttribute: Updated prod version of ManualAttribute at version 0.1. git-subtree-dir: ManualAttribute git-subtree-split: d1d0693dc684f649ca16c9e3c1b8e95dd81ab012 --- package.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000000..c6a0e254c7 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "ManualAttribute", + "version": "0.1", + "description": "Creates a manual copy of an autocalculated field, and assigns it to the bar of a token.", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + }, + "modifies": { + "attribute": "create", + "attribute.current": "write", + "attribute.max": "read,write", + "graphic.bar*_link": "write", + "graphic.represents": "read" + }, + "conflicts": [ + ] +} From 4bebac0a0728f3b49331a7da6ad83012dbb3f12f Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:27 -0600 Subject: [PATCH 37/42] Squashed 'MonsterHitDice/' changes from 0ebf115..ed8eb62 ed8eb62 prod-MonsterHitDice: Updated prod version of MonsterHitDice at version . git-subtree-dir: MonsterHitDice git-subtree-split: ed8eb62fac17c76b4fda556aad957c30ebdd3607 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bbf029aafd..3eccb2e377 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "MonsterHitDice", - "version": "", + "version": "0.1", "description": "Set Monster hit dice on add, usually via drag from journal.", "authors": "The Aaron", "roll20userid": "104025", "dependencies": { }, "modifies": { - "attribute.current": "read" + "attribute.current": "read", "attribute.name": "read", "attribute.type": "read", "character.represents": "read", From 1b5ca7753117c567d79fc2c2de1d964dcc7cefef Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:31 -0600 Subject: [PATCH 38/42] Squashed 'MonsterHitDice5e/' changes from c2ae699..a55d3ee a55d3ee prod-MonsterHitDice5e: Updated prod version of MonsterHitDice5e at version . git-subtree-dir: MonsterHitDice5e git-subtree-split: a55d3eee386e47cdb0a68efcf2afd9214219b647 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 893cd04dba..001d7c5132 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "MonsterHitDice5e", - "version": "", + "version": "0.1", "description": "Set Monster hit dice on add, usually via drag from journal. Configured for Dungeons and Dragons 5e Character Sheet", "authors": "The Aaron", "roll20userid": "104025", "dependencies": { }, "modifies": { - "attribute.current": "read" + "attribute.current": "read", "attribute.name": "read", "attribute.type": "read", "character.represents": "read", From 3e0d3009c6915db00c406c8cc62e4dcd294172cd Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:39 -0600 Subject: [PATCH 39/42] Squashed 'SpellLevel5e/' changes from eb49f34..c0c723a c0c723a prod-SpellLevel5e: Updated prod version of SpellLevel5e at version 0.1. git-subtree-dir: SpellLevel5e git-subtree-split: c0c723aadf5c488c1681cf680b15902adcc3957c --- package.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000000..3ec29eaa7f --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "SpellLevel5e", + "version": "0.1", + "description": "", + "authors": "The Aaron", + "roll20userid": "104025", + "dependencies": { + "isGMModule": "0.x" + }, + "modifies": { + "state.SpellLevel5e": "read,write", + "attribute": "create", + "attribute.characterid": "read", + "attribute.current": "read,write", + "attribute.name": "read" + }, + "conflicts": [ + ] +} From 416abe079d064765ef053608f48be0942ea530f9 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:45 -0600 Subject: [PATCH 40/42] Squashed 'SpinTokens/' changes from 721678b..a9b5eb7 a9b5eb7 prod-SpinTokens: Updated prod version of SpinTokens at version 0.3. git-subtree-dir: SpinTokens git-subtree-split: a9b5eb7050c547e21b7d021cc1cfd225a959be04 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a6d710a70..6775f7d324 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "campaign.playerspecificpages": "read", "graphic.pageid": "read", "graphic.rotation": "write", - "graphic.subtype": "read", + "graphic.subtype": "read" }, "conflicts": [ ] From 7b2fa3f6db2b3af25e113d392c5c3b2a407db63d Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:49 -0600 Subject: [PATCH 41/42] Squashed 'TableTokenSizer/' changes from b809e4a..8cfd167 8cfd167 prod-TableTokenSizer: Updated prod version of TableTokenSizer at version 0.1. git-subtree-dir: TableTokenSizer git-subtree-split: 8cfd167b0fd6214731e70e0b9e1b65039507008c --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5129e7e232..3a02b6a078 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "modifies": { "graphic.height": "write", - "graphic.isdrawing": "write" + "graphic.isdrawing": "write", "graphic.sides": "read", "graphic.width": "read,write" }, From 0545eb5874ee212b946eac6635b631914f0a5777 Mon Sep 17 00:00:00 2001 From: "Aaron C. Meadows" Date: Mon, 2 Feb 2015 00:47:55 -0600 Subject: [PATCH 42/42] Squashed 'TokenNameNumber/' changes from 0d25edc..a16aee4 a16aee4 prod-TokenNameNumber: Updated prod version of TokenNameNumber at version 0.2. git-subtree-dir: TokenNameNumber git-subtree-split: a16aee491dce01a3ba4256008bb902d4e58022c2 --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0b643ca8e8..e35fb34e1f 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ }, "modifies": { "state.TokenNameNumber": "read,write", - "graphic.name", "read,write", - "graphic.pageid", "read", - "graphic.represents", "read", - "graphic.subtype", "read", - "graphic.type", "read" + "graphic.name": "read,write", + "graphic.pageid": "read", + "graphic.represents": "read", + "graphic.subtype": "read", + "graphic.type": "read" }, "conflicts": [ ]