// 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 globalconfig = globalconfig || undefined; var TokenNameNumber = TokenNameNumber || (function() { 'use strict'; var version = '0.5.12', lastUpdate = 1500994908, schemaVersion = 0.5, statuses = [ 'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', // 0-6 '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' ], statusColormap = ['#C91010', '#1076c9', '#2fc910', '#c97310', '#9510c9', '#eb75e1', '#e5eb75'], regex = { escape: /(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g }, tokenIds = [], checkGlobalConfig = function(){ var s=state.TokenNameNumber, g=globalconfig && globalconfig.tokennamenumber, parsedDots; if(g && g.lastsaved && g.lastsaved > s.globalconfigCache.lastsaved ){ log(' > Updating from Global Config < ['+(new Date(g.lastsaved*1000))+']'); s.config.randomSpace = parseInt(g['Random Space'])||0; s.config.useDots = 'useDots' === g['Use Dots']; if(s.config.useDots){ parsedDots=_.chain(g.Dots.match(/[a-zA-Z-]+/g)) .map(function(s){ return s.toLowerCase();}) .uniq() .filter(function(s){ return _.contains(statuses,s);}) ; if(parsedDots.length){ s.config.dots = parsedDots; } } state.TokenNameNumber.globalconfigCache=globalconfig.tokennamenumber; } }, checkInstall = function() { log('-=> TokenNameNumber v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); if( ! _.has(state,'TokenNameNumber') || state.TokenNameNumber.version !== schemaVersion) { log(' > Updating Schema to v'+schemaVersion+' <'); switch(state.TokenNameNumber && state.TokenNameNumber.version) { case 0.3: case 0.4: delete state.TokenNameNumber.globalConfigCache; state.TokenNameNumber.globalconfigCache = {lastsaved:0}; /* falls through */ case 'UpdateSchemaVersion': state.TokenNameNumber.version = schemaVersion; break; default: state.TokenNameNumber = { version: schemaVersion, globalconfigCache: {lastsaved:0}, config: { randomSpace: 0, useDots: false, dots: ['red','brown','yellow','green','blue','purple'] }, registry: { } }; break; } } checkGlobalConfig(); }, esRE = function (s) { return s.replace(regex.escape,"\\$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 ''; }, getConfigOption_RandomSpace = function() { var text = ( state.TokenNameNumber.config.randomSpace > 0 ? ''+ state.TokenNameNumber.config.randomSpace+ '' : 'Off' ); return '
'+ 'Random Space of numbers between each consecutively generated token number:'+ text+'. '+ ''+ 'Set Random Space'+ ''+ '
'; }, getConfigOption_UseDots = function() { var text = (state.TokenNameNumber.config.useDots ? 'On' : 'Off'); return '
'+ 'Use Dots is currently '+ text+ '.'+ 'Toggle'+ '
'; }, getStatusButton = function(status) { var i=_.indexOf(statuses,status); if(i<7) { return '
'; } return '
'; }, // https://app.roll20.net/images/statussheet.png getConfigOption_Dots = function() { return '
'+ '
'+ '
Dots (Click to move between pools).
'+ '
'+ _.map(state.TokenNameNumber.config.dots,function(s){ return getStatusButton(s); }).join('')+ '
'+ '
'+ '
'+ '
Available Statuses
'+ '
'+ _.map(_.difference(statuses,state.TokenNameNumber.config.dots),function(s){ return getStatusButton(s); }).join('')+ '
'+ '
'+ '
'; }, getAllConfigOptions = function() { return ''; }, showHelp = function(who) { sendChat('','/w '+who+' '+ '
'+ '
'+ 'TokenNameNumber v'+version+ '
'+ '
'+ '

Provides automatic numbering of tokens dragged into onto the tabletop. Token names need to have the special word %%NUMBERED%% somewhere in them.

'+ '
'+ 'Commands'+ '
'+ '!tnn [--help]'+ '
'+ '

Currently, this just displays the help, which is used for configuring.

'+ '
    '+ '
  • '+ '--help '+ch('-')+' Displays the help and configuration options.'+ '
  • '+ '
'+ '
'+ '
'+ 'Config'+ '
'+ getAllConfigOptions()+ '
'+ '
' ); }, handleInput = function(msg) { var args, who; if (msg.type !== "api" || !playerIsGM(msg.playerid)) { return; } who=(getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); args = msg.content.split(/\s+--/); switch(args.shift()) { case '!tnn': showHelp(who); break; case '!tnn-config': if(_.contains(args,'help')) { showHelp(who); return; } if(!args.length) { sendChat('','/w '+who+' '+ '
'+ '
'+ 'TokenNameNumber v'+version+ '
'+ getAllConfigOptions()+ '
' ); return; } _.each(args,function(a){ var opt=a.split(/\|/); switch(opt.shift()) { case 'toggle-use-dots': state.TokenNameNumber.config.useDots=!state.TokenNameNumber.config.useDots; sendChat('','/w '+who+' '+ '
'+ getConfigOption_UseDots()+ '
' ); break; case 'toggle-dot': if(_.contains(state.TokenNameNumber.config.dots, opt[0])){ state.TokenNameNumber.config.dots = _.without(state.TokenNameNumber.config.dots, opt[0]); } else { state.TokenNameNumber.config.dots.push(opt[0]); } sendChat('','/w '+who+' '+ '
'+ getConfigOption_Dots()+ '
' ); break; case 'random-space': state.TokenNameNumber.config.randomSpace=parseInt(opt[0],10); sendChat('','/w '+who+' '+ '
'+ getConfigOption_RandomSpace()+ '
' ); break; default: sendChat('','/w '+who+' '+ '
Unsupported Option:
'+a+'' ); } }); break; } }, 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); } }, getDotNumber = function(num) { var base = (function (b){ var radix = b; return (b && function base_dot(number,digits){ digits = digits || []; digits.push(number % radix); if(number < radix) { return digits; } return base_dot(Math.floor(number/radix),digits); }) || function(){return [];}; }(state.TokenNameNumber.config.dots.length)); return base(num); }, saveTokenId = function(obj){ tokenIds.push(obj.id); let token_id = obj.id; _.delay(()=>{ var token=getObj('graphic',token_id); if(token){ setNumberOnToken(token); } },100); _.delay(()=>{ tokenIds=_.without(tokenIds,token_id); },1000); }, setNumberOnToken = function(obj) { var matchers, tokenName, matcher, renamer, parts, num, statuspart=''; if(_.contains(tokenIds,obj.id)){ if( 'graphic' === obj.get('type') && 'token' === obj.get('subtype') ) { matchers = (getMatchers(obj.get('pageid'), obj.get('represents'))) || []; tokenName = (obj.get('name')); if(tokenName.match( /%%NUMBERED%%/ ) || _.some(matchers,function(m) { return m.test(tokenName);}) ) { tokenIds=_.without(tokenIds,obj.id); if( 0 === matchers.length || !_.some(matchers,function(m) { return m.test(tokenName);}) ) { matcher='^('+esRE(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('^('+esRE(tokenName).replace(/%%NUMBERED%%/,')(\\d+)(')+')$'); renamer=new RegExp('^('+esRE(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() ); num += ( state.TokenNameNumber.config.randomSpace ? (randomInteger(state.TokenNameNumber.config.randomSpace)-1) : 0); if(state.TokenNameNumber.config.useDots) { statuspart = _.map(getDotNumber(num), function(n){ return state.TokenNameNumber.config.dots[n]; }).join(','); if(statuspart) { obj.set({ statusmarkers: statuspart }); } } parts=renamer.exec(tokenName); obj.set({ name: parts[1]+(++num)+parts[3] }); } } } }, registerEventHandlers = function() { on('chat:message', handleInput); on('add:graphic', saveTokenId); on('change:graphic', setNumberOnToken); }; return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers, NotifyOfCreatedToken: saveTokenId }; }()); on("ready",function(){ 'use strict'; TokenNameNumber.CheckInstall(); TokenNameNumber.RegisterEventHandlers(); });