// 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.9.19', lastUpdate = 1473946107, schemaVersion = 1.0, bonusCache = {}, observers = { turnOrderChange: [] }, sorters = { 'None': function(to) { return to; }, 'Ascending': function(to){ return _.sortBy(to,function(i){ return (i.pr); }); }, 'Descending': function(to){ return _.sortBy(to,function(i){ return (-i.pr); }); } }, esRE = function (s) { var escapeForRegexp = /(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g; return s.replace(escapeForRegexp,"\\$1"); }, HE = (function(){ var entities={ //' ' : '&'+'nbsp'+';', '<' : '&'+'lt'+';', '>' : '&'+'gt'+';', "'" : '&'+'#39'+';', '@' : '&'+'#64'+';', '{' : '&'+'#123'+';', '|' : '&'+'#124'+';', '}' : '&'+'#125'+';', '[' : '&'+'#91'+';', ']' : '&'+'#93'+';', '"' : '&'+'quot'+';' }, re=new RegExp('('+_.map(_.keys(entities),esRE).join('|')+')','g'); return function(s){ return s.replace(re, function(c){ return entities[c] || c; }); }; }()), observeTurnOrderChange = function(handler){ if(handler && _.isFunction(handler)){ observers.turnOrderChange.push(handler); } }, notifyObservers = function(event){ _.each(observers[event],function(handler){ handler(); }); }, formatDieRoll = function(rollData) { var critFail = _.reduce(rollData.rolls,function(m,r){ return m || _.contains(r.rolls,1); },false), critSuccess = _.reduce(rollData.rolls,function(m,r){ return m || _.contains(r.rolls,r.sides); },false), highlight = ( (critFail && critSuccess) ? '#4A57ED' : ( critFail ? '#B31515' : ( critSuccess ? '#3FB315' : '#FEF68E' ) ) ), dicePart = _.reduce(rollData.rolls, function(m,r){ _.reduce(r.rolls,function(dm,dr){ var dielight=( 1 === dr ? '#ff0000' : ( r.sides === dr ? '#00ff00' : 'white' ) ); dm.push(''+dr+''); return dm; },m); return m; },[]).join(' + '); return ''+ dicePart+' [init] '+ (rollData.bonus>=0 ? '+' :'-')+' '+Math.abs(rollData.bonus)+' [bonus]'+ '' ))+'">'+ rollData.total+ ''; }, buildAnnounceGroups = function(l) { var groupColors = { npc: '#eef', character: '#efe', gmlayer: '#aaa' }; return _.reduce(l,function(m,s){ var type= ('gmlayer' === s.token.get('layer') ? 'gmlayer' : ( (s.character && _.filter(s.character.get('controlledby').split(/,/),function(c){ return 'all' === c || ('' !== c && !playerIsGM(c) ); }).length>0) || false ? 'character' : 'npc' )); if('graphic'!==s.token.get('type') || 'token' !==s.token.get('subtype')) { return m; } m[type].push('
'+ '
'+ ''+ ((s.token && s.token.get('name')) || (s.character && s.character.get('name')) || '(Creature)')+ '
'+ '
'+ formatDieRoll(s.rollResults)+ '
'+ '
'+ '
'); return m; },{npc:[],character:[],gmlayer:[]}); }, announcers = { 'None': function() { }, 'Hidden': function(l) { var groups=buildAnnounceGroups(l); sendChat('GroupInit','/w gm '+ '
'+ groups.character.join('')+ groups.npc.join('')+ groups.gmlayer.join('')+ '
'+ '
'); }, 'Partial': function(l) { var groups=buildAnnounceGroups(l); sendChat('GroupInit','/direct '+ '
'+ groups.character.join('')+ '
'+ '
'); sendChat('GroupInit','/w gm '+ '
'+ groups.npc.join('')+ groups.gmlayer.join('')+ '
'+ '
'); }, 'Visible': function(l) { var groups=buildAnnounceGroups(l); sendChat('GroupInit','/direct '+ '
'+ groups.character.join('')+ groups.npc.join('')+ '
'+ '
'); sendChat('GroupInit','/w gm '+ '
'+ groups.gmlayer.join('')+ '
'+ '
'); } }, statAdjustments = { 'Stat-DnD': { func: function(v) { return '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 'floor('+v+')'; }, desc: 'Rounds down to the nearest integer.' }, 'Tie-Breaker': { func: function(v) { return '(0.01*('+v+'))'; }, desc: 'Adds the accompanying attribute as a decimal (0.01)' }, 'Ceiling': { func: function(v) { return 'ceil('+v+')'; }, desc: 'Rounds up to the nearest integer.' }, 'Bounded': { func: function(v) { return v; }, desc: 'DEPREICATED - will not work with expresions.' } }, buildInitDiceExpression = function(s){ var stat=(''!== state.GroupInitiative.config.diceCountAttribute && s.character && getAttrByName(s.character.id, state.GroupInitiative.config.diceCountAttribute, 'current')); if(stat ) { stat = (_.isString(stat) ? stat : stat+''); if('0' !== stat) { stat = stat.replace(/@\{([^\|]*?|[^\|]*?\|max|[^\|]*?\|current)\}/g, '@{'+(s.character.get('name'))+'|$1}'); return '('+stat+')d'+state.GroupInitiative.config.dieSize; } } return state.GroupInitiative.config.diceCount+'d'+state.GroupInitiative.config.dieSize; }, rollers = { 'Least-All-Roll':{ mutator: function(l){ var min=_.reduce(l,function(m,r){ if(!m || (r.total < m.total)) { return r; } return m; },false); return _.times(l.length, function(){ return min; }); }, func: function(s){ return buildInitDiceExpression(s); }, desc: 'Sets the initiative to the lowest of all initiatives rolled for the group.' }, 'Mean-All-Roll':{ mutator: function(l){ var mean = l[Math.round((l.length/2)-0.5)]; return _.times(l.length, function(){ return mean; }); }, func: function(s){ return buildInitDiceExpression(s); }, desc: 'Sets the initiative to the mean (average) of all initiatives rolled for the group.' }, 'Individual-Roll': { mutator: function(l){ return l; }, func: function(s){ return buildInitDiceExpression(s); }, desc: 'Sets the initiative individually for each member of the group.' }, 'Constant-By-Stat': { mutator: function(l){ return l; }, func: function(){ return '0'; }, desc: 'Sets the initiative individually for each member of the group to their bonus with no roll.' } }, checkInstall = function() { log('-=> GroupInitiative v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); if( ! _.has(state,'GroupInitiative') || state.GroupInitiative.version !== schemaVersion) { log(' > Updating Schema to v'+schemaVersion+' <'); switch(state.GroupInitiative && state.GroupInitiative.version) { case 0.5: state.GroupInitiative.replaceRoll = false; /* break; // intentional dropthrough */ /* falls through */ case 0.6: state.GroupInitiative.config = { rollType: state.GroupInitiative.rollType, replaceRoll: state.GroupInitiative.replaceRoll, dieSize: 20, autoOpenInit: true, sortOption: 'Descending' }; delete state.GroupInitiative.replaceRoll; delete state.GroupInitiative.rollType; /* break; // intentional dropthrough */ /* falls through */ case 0.7: state.GroupInitiative.config.announcer = 'Partial'; /* break; // intentional dropthrough */ /* falls through */ case 0.8: state.GroupInitiative.config.diceCount = 1; state.GroupInitiative.config.maxDecimal = 2; /* break; // intentional dropthrough */ /* falls through */ case 0.9: state.GroupInitiative.config.diceCountAttribute = ''; /* break; // intentional dropthrough */ /* falls through */ case 0.10: if(_.has(state.GroupInitiative.config,'dieCountAttribute')){ delete state.GroupInitiative.config.dieCountAttribute; state.GroupInitiative.config.diceCountAttribute = ''; } if(_.has(state.GroupInitiative.config,'dieCount')){ delete state.GroupInitiative.config.dieCount; state.GroupInitiative.config.diceCount = 1; } /* break; // intentional dropthrough */ /* falls through */ case 'UpdateSchemaVersion': state.GroupInitiative.version = schemaVersion; break; default: state.GroupInitiative = { version: schemaVersion, bonusStatGroups: [ [ { attribute: 'dexterity' } ] ], config: { rollType: 'Individual-Roll', replaceRoll: false, dieSize: 20, diceCount: 1, maxDecimal: 2, diceCountAttribute: '', autoOpenInit: true, sortOption: 'Descending', announcer: 'Partial' } }; break; } } }, 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+"
  • "; },""); }, getConfigOption_SortOptions = function() { var text = state.GroupInitiative.config.sortOption; return '
    '+ 'Sort Options is currently '+ text+ '.'+ '
    '+ _.map(_.keys(sorters),function(so){ return ''+ so+ ''; }).join(' ')+ '
    '+ '
    '; }, getConfigOption_DieSize = function() { return '
    '+ 'Initiative Die size is currently '+ state.GroupInitiative.config.dieSize+ ' '+ ''+ 'Set Die Size'+ ''+ '
    '; }, getConfigOption_DiceCount = function() { return '
    '+ 'Initiative Dice Count is currently '+ state.GroupInitiative.config.diceCount+ ' '+ ''+ 'Set Dice Count'+ ''+ '
    '; }, getConfigOption_MaxDecimal = function() { return '
    '+ 'Max decimal places '+ state.GroupInitiative.config.maxDecimal+ ' '+ ''+ 'Set Max Decimal'+ ''+ '
    '; }, getConfigOption_DiceCountAttribute = function() { var text = (state.GroupInitiative.config.diceCountAttribute.length ? state.GroupInitiative.config.diceCountAttribute : 'DISABLED'); return '
    '+ 'Dice Count Attribute: '+ text+ ' '+ ''+ 'Set Attribute'+ ''+ '
    '; }, getConfigOption_AutoOpenInit = function() { var text = (state.GroupInitiative.config.autoOpenInit ? 'On' : 'Off' ); return '
    '+ 'Auto Open Init is currently '+ text+ ' '+ ''+ 'Toggle'+ ''+ '
    '; }, getConfigOption_ReplaceRoll = function() { var text = (state.GroupInitiative.config.replaceRoll ? 'On' : 'Off' ); return '
    '+ 'Replace Roll is currently '+ text+ ' '+ ''+ 'Toggle'+ ''+ '

    Sets whether initative scores for selected tokens replace their current scores.

    '+ '
    '; }, getConfigOption_RollerOptions = function() { var text = state.GroupInitiative.config.rollType; return '
    '+ 'Roller is currently '+ text+ '.'+ '
    '+ _.map(_.keys(rollers),function(r){ return ''+ r+ ''; }).join(' ')+ '
    '+ '
    '; }, getConfigOption_AnnounceOptions = function() { var text = state.GroupInitiative.config.announcer; return '
    '+ 'Announcer is currently '+ text+ '.'+ '
    '+ _.map(_.keys(announcers),function(an){ return ''+ an+ ''; }).join(' ')+ '
    '+ '
    '; }, getAllConfigOptions = function() { return getConfigOption_RollerOptions() + getConfigOption_SortOptions() + getConfigOption_DieSize() + getConfigOption_DiceCount() + getConfigOption_DiceCountAttribute() + getConfigOption_MaxDecimal() + getConfigOption_AutoOpenInit() + getConfigOption_ReplaceRoll() + getConfigOption_AnnounceOptions(); }, showHelp = function() { var rollerRows=_.reduce(rollers,function(memo,r,n){ var selected=((state.GroupInitiative.config.rollType === n) ? '
    Selected
    ' : '' ), selectedStyleExtra=((state.GroupInitiative.config.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 --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).'+ '
    • '+ '
    '+ '
    '+ '
    '+ '
    '+ '!group-init --reroll '+ch('[')+'bonus'+ch(']')+''+ '
    '+ '

    Rerolls all the tokens in the turn order as if they were selected when you executed the bare !group-init command.

    '+ 'This command has 1 optional parameter:'+ '
      '+ '
    • '+ 'bonus -- The value to be added to all the rolls. This can be positive or negative and can have decimal places.'+ '
    • '+ '
    '+ '
    '+ '
    '+ '
    '+ '!group-init --bonus '+ch('<')+'number'+ch('>')+''+ '
    '+ '

    Rolls the selected tokens and adds a bonus to the roll.

    '+ 'This command requires 1 parameter:'+ '
      '+ '
    • '+ 'number -- The value to be added to all the rolls. This can be positive or negative and can have decimal places.'+ '
    • '+ '
    '+ '
    '+ '
    '+ '
    '+ '!group-init --adjust '+ch('<')+'adjustment'+ch('>')+' '+ch('[')+'minimum'+ch(']')+''+ '
    '+ '

    Applies an adjustment to all the current turnorder tokens (Custom entries ignored)

    '+ 'This command requires 1 parameter and has 1 optional parameter:'+ '
      '+ '
    • '+ 'adjustment -- The value to be added to all the current initiative values. This can be positive or negative and can have decimal places.'+ '
    • '+ '
    • '+ 'minimum -- (optional) The minimum value for all initiative scores. This can be positive or negative and can have decimal places.
      Note: this can raise initiative scores if it is higher than their current score.'+ '
    • '+ '
    '+ '
    '+ '
    '+ '
    '+ '!group-init --clear'+ '
    '+ '

    Removes all tokens from the turn order. If Auto Open Init is enabled it will also close the turn order box.

    '+ '
    '+ '
    '+ 'Roller Options'+ '
    '+ ''+ '
    '+ 'Stat Adjustment Options'+ '
    '+ ''+ '
    '+ 'Bonus Stat Groups'+ '
    '+ '
      '+ bonusStatGroupRows+ '
    '+ '
    '+ getAllConfigOptions()+ '
    ' ); }, findInitiativeBonus = function(charObj,token) { var bonus = ''; if(_.has(bonusCache,charObj.id)) { return bonusCache[charObj.id]; } _.find(state.GroupInitiative.bonusStatGroups, function(group){ bonus = _.chain(group) .map(function(details){ var stat=getAttrByName(charObj.id,details.attribute, details.type||'current'); if( ! _.isUndefined(stat) && !_.isNull(stat) ) { stat = (stat+'').replace(/@\{([^\|]*?|[^\|]*?\|max|[^\|]*?\|current)\}/g, '@{'+(charObj.get('name'))+'|$1}'); 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; } return undefined; }) .value(); if(_.contains(bonus,undefined) || _.contains(bonus,null) || _.contains(bonus,NaN)) { bonus=''; return false; } bonus = bonus.join('+'); return true; }); bonusCache[charObj.id]=bonus; return bonus; }, handleInput = function(msg_orig) { var msg = _.clone(msg_orig), args, cmds, workgroup, workvar, turnorder, pageid=false, error=false, initFunc, rollSetup, initRolls, cont=false, manualBonus=0, manualBonusMin=0, turnEntries, finalize, isReroll=false ; 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 '!group-init': if(args.length > 0) { cmds=args.shift().split(/\s+/); switch(cmds[0]) { case 'help': if(!playerIsGM(msg.playerid)){ return; } showHelp(); break; case 'add-group': if(!playerIsGM(msg.playerid)){ return; } 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 Adjustment: '+c[0]+'
    '+ 'Use one of the following:'+ ''+ '
    ' ); 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': if(!playerIsGM(msg.playerid)){ return; } 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': if(!playerIsGM(msg.playerid)){ return; } 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 'reroll': isReroll=true; if(cmds[1] && cmds[1].match(/^[\-\+]?\d+(\.\d+)?$/)){ manualBonus=parseFloat(cmds[1])||0; } msg.selected= _.chain(JSON.parse(Campaign().get('turnorder'))||[]) .filter(function(e){ return '-1' !== e.id; }) .map(function(e){ return {_type: 'graphic', _id: e.id}; }) .value(); cont=true; break; case 'adjust': if(cmds[1] && cmds[1].match(/^[\-\+]?\d+(\.\d+)?$/)){ manualBonus=parseFloat(cmds[1]); manualBonusMin=parseFloat(cmds[2]); manualBonusMin=_.isNaN(manualBonusMin)?-10000:manualBonusMin; Campaign().set({ turnorder: JSON.stringify( _.map(JSON.parse(Campaign().get('turnorder'))||[], function(e){ if('-1' !== e.id){ e.pr=Math.max(e.pr+manualBonus,manualBonusMin); } return e; }) ) }); notifyObservers('turnOrderChange'); } else { sendChat('GroupInitiative', '/w gm ' + '
    '+ 'Not a valid adjustment: '+cmds[1]+''+ '
    ' ); } break; case 'clear': Campaign().set({ turnorder: '[]', initiativepage: (state.GroupInitiative.config.autoOpenInit ? false : Campaign().get('initiativepage')) }); notifyObservers('turnOrderChange'); break; case 'bonus': if(cmds[1] && cmds[1].match(/^[\-\+]?\d+(\.\d+)?$/)){ manualBonus=parseFloat(cmds[1]); cont=true; } else { sendChat('GroupInitiative', '/w gm ' + '
    '+ 'Not a valid bonus: '+cmds[1]+''+ '
    ' ); } break; default: if(!playerIsGM(msg.playerid)){ return; } sendChat('GroupInitiative', '/w gm ' + '
    '+ 'Not a valid command: '+cmds[0]+''+ '
    ' ); break; } } else { cont=true; } if(cont) { if(_.has(msg,'selected')) { bonusCache = {}; turnorder = Campaign().get('turnorder'); turnorder = ('' === turnorder) ? [] : JSON.parse(turnorder); if(state.GroupInitiative.config.replaceRoll || isReroll) { turnorder=_.reject(turnorder,function(i){ return _.contains(_.pluck(msg.selected, '_id'),i.id); }); } initFunc=rollers[state.GroupInitiative.config.rollType].func; rollSetup = _.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){ pageid=pageid || s.get('pageid'); return { token: s, character: getObj('character',s.get('represents')) }; }) .map(function(s){ s.roll=[]; if(s.character) { s.roll.push( findInitiativeBonus(s.character,s.token) ); } if(manualBonus) { s.roll.push( manualBonus ); } s.roll.push( initFunc(s) ); return s; }) .value(); initRolls = _.map(rollSetup,function(rs,i){ return { index: i, roll: ('[[('+ _.reject(rs.roll,function(r){ return _.isString(r) && _.isEmpty(r); }) .join(') + (')+')]]') .replace(/\[\[\[/g, "[[ [") }; }); turnEntries = []; finalize = _.after(initRolls.length,function(){ turnEntries = _.sortBy(turnEntries,'order'); turnEntries = rollers[state.GroupInitiative.config.rollType].mutator(turnEntries); Campaign().set({ turnorder: JSON.stringify( sorters[state.GroupInitiative.config.sortOption]( turnorder.concat( _.chain(rollSetup) .map(function(s){ s.rollResults=turnEntries.shift(); return s; }) .tap(announcers[state.GroupInitiative.config.announcer]) .map(function(s){ return { id: s.token.id, pr: s.rollResults.total, custom: '' }; }) .value() ) ) ) }); notifyObservers('turnOrderChange'); if(state.GroupInitiative.config.autoOpenInit && !Campaign().get('initativepage')) { Campaign().set({ initiativepage: pageid }); } }); _.each(initRolls, function(ir){ sendChat('',ir.index+':'+ir.roll,function(msg){ var parts = msg[0].content.split(/:/), ird = msg[0].inlinerolls[parts[1].match(/\d+/)], rdata = { order: parseInt(parts[0],10), total: (ird.results.total%1===0 ? ird.results.total : parseFloat(ird.results.total.toFixed(state.GroupInitiative.config.maxDecimal))), rolls: _.reduce(ird.results.rolls,function(m,rs){ if('R' === rs.type) { m.push({ sides: rs.sides, rolls: _.pluck(rs.results,'v') }); } return m; },[]) }; rdata.bonus = (ird.results.total - (_.reduce(rdata.rolls,function(m,r){ m+=_.reduce(r.rolls,function(s,dieroll){ return s+dieroll; },0); return m; },0))); rdata.bonus = (rdata.bonus%1===0 ? rdata.bonus : parseFloat(rdata.bonus.toFixed(state.GroupInitiative.config.maxDecimal))); turnEntries.push(rdata); finalize(); }); }); } else { showHelp(); } } break; case '!group-init-config': if(!playerIsGM(msg.playerid)){ return; } if(_.contains(args,'--help')) { showHelp(); return; } if(!args.length) { sendChat('','/w gm '+ '
    '+ '
    '+ 'GroupInitiative v'+version+ '
    '+ getAllConfigOptions()+ '
    ' ); return; } _.each(args,function(a){ var opt=a.split(/\|/), omsg=''; switch(opt.shift()) { case 'sort-option': if(sorters[opt[0]]) { state.GroupInitiative.config.sortOption=opt[0]; } else { omsg='
    Error: Not a valid sort method: '+opt[0]+'
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_SortOptions()+ '
    ' ); break; case 'set-die-size': if(opt[0].match(/^\d+$/)) { state.GroupInitiative.config.dieSize=parseInt(opt[0],10); } else { omsg='
    Error: Not a die size: '+opt[0]+'
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_DieSize()+ '
    ' ); break; case 'set-max-decimal': if(opt[0].match(/^\d+$/)) { state.GroupInitiative.config.maxDecimal=parseInt(opt[0],10); } else { omsg='
    Error: Not a valid decimal count: '+opt[0]+'
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_MaxDecimal()+ '
    ' ); break; case 'set-dice-count': if(opt[0].match(/^\d+$/)) { state.GroupInitiative.config.diceCount=parseInt(opt[0],10); } else { omsg='
    Error: Not a valid dice count: '+opt[0]+'
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_DiceCount()+ '
    ' ); break; case 'set-dice-count-attribute': if(opt[0]) { state.GroupInitiative.config.diceCountAttribute=opt[0]; } else { state.GroupInitiative.config.diceCountAttribute=''; omsg='
    Cleared Dice Count Attribute.
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_DiceCountAttribute()+ '
    ' ); break; case 'toggle-auto-open-init': state.GroupInitiative.config.autoOpenInit = !state.GroupInitiative.config.autoOpenInit; sendChat('','/w gm '+ '
    '+ getConfigOption_AutoOpenInit()+ '
    ' ); break; case 'toggle-replace-roll': state.GroupInitiative.config.replaceRoll = !state.GroupInitiative.config.replaceRoll; sendChat('','/w gm '+ '
    '+ getConfigOption_ReplaceRoll()+ '
    ' ); break; case 'set-announcer': if(announcers[opt[0]]) { state.GroupInitiative.config.announcer=opt[0]; } else { omsg='
    Error: Not a valid announcer: '+opt[0]+'
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_AnnounceOptions()+ '
    ' ); break; case 'set-roller': if(rollers[opt[0]]) { state.GroupInitiative.config.rollType=opt[0]; } else { omsg='
    Error: Not a valid roller: '+opt[0]+'
    '; } sendChat('','/w gm '+ '
    '+ omsg+ getConfigOption_RollerOptions()+ '
    ' ); break; default: sendChat('','/w gm '+ '
    Unsupported Option:
    '+a+'' ); } }); break; } }, registerEventHandlers = function() { on('chat:message', handleInput); }; return { RegisterEventHandlers: registerEventHandlers, ObserveTurnOrderChange: observeTurnOrderChange, CheckInstall: checkInstall }; }()); on("ready",function(){ 'use strict'; GroupInitiative.CheckInstall(); GroupInitiative.RegisterEventHandlers(); });