Skip to content

Commit

Permalink
feat(config): Check against sheet version on startup
Browse files Browse the repository at this point in the history
closes #12
  • Loading branch information
symposion committed Feb 26, 2017
1 parent 44104d0 commit 35ba538
Show file tree
Hide file tree
Showing 29 changed files with 751 additions and 684 deletions.
84 changes: 84 additions & 0 deletions lib/chat-watcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

const _ = require('underscore');

module.exports = class ChatWatcher {
constructor(roll20, logger, eventDispatcher) {
this.roll20 = roll20;
this.logger = logger;
this.eventDispatcher = eventDispatcher;
this.chatListeners = [];
logger.wrapModule(this);
eventDispatcher.registerEventHandler('chat:message', (msg) => {
if (msg.type !== 'api') {
this.triggerChatListeners(msg);
}
});
}

registerChatListener(triggerFields, handler) {
const matchers = [];
if (triggerFields && !_.isEmpty(triggerFields)) {
matchers.push((msg, options) => {
this.logger.debug('Matching options: $$$ against triggerFields $$$', options, triggerFields);
return _.intersection(triggerFields, _.keys(options)).length === triggerFields.length;
});
}
this.chatListeners.push({ matchers, handler });
}

triggerChatListeners(msg) {
const options = this.getRollTemplateOptions(msg);
this.logger.debug('Roll template options: $$$', options);
_.each(this.chatListeners, (listener) => {
if (_.every(listener.matchers, matcher => matcher(msg, options))) {
listener.handler(options, msg);
}
});
}

/**
*
* @returns {*}
*/
getRollTemplateOptions(msg) {
if (msg.rolltemplate === '5e-shaped') {
const regex = /\{\{(.*?)}}/g;
let match;
const options = {};
while ((match = regex.exec(ChatWatcher.processInlinerolls(msg)))) {
if (match[1]) {
const splitAttr = match[1].split('=');
const propertyName = splitAttr[0].replace(/_([a-z])/g, (m, letter) => letter.toUpperCase());
options[propertyName] = splitAttr.length === 2 ? splitAttr[1].replace(/\^\{/, '') : '';
}
}
if (options.characterName) {
options.character = this.roll20.findObjs({
_type: 'character',
name: options.characterName,
})[0];
}
return options;
}
return {};
}

static processInlinerolls(msg) {
if (_.has(msg, 'inlinerolls')) {
return _.chain(msg.inlinerolls)
.reduce((previous, current, index) => {
previous[`$[[${index}]]`] = current.results.total || 0;
return previous;
}, {})
.reduce((previous, current, index) => previous.replace(index.toString(), current), msg.content)
.value();
}

return msg.content;
}

get logWrap() {
return 'ChatWatcher';
}
};
30 changes: 10 additions & 20 deletions lib/command-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
const _ = require('underscore');
const utils = require('./utils');
const UserError = require('./user-error');
const ShapedModule = require('./shaped-module');


function getParser(optionString, validator) {
Expand Down Expand Up @@ -270,27 +269,10 @@ function processSelection(selection, constraints, roll20, requiredCharVersion) {
}, {});
}

module.exports = function commandParser(rootCommand, roll20, errorHandler) {
module.exports = function commandParser(rootCommand, roll20, errorHandler, eventDispatcher, requiredCharVersion) {
const commands = {};
let requiredCharVersion = null;
return {
setRequiredCharacterVersion(version) {
requiredCharVersion = version;
},

addCommand(cmds, handler, gmOnly) {
const command = new Command(this, handler, roll20, _.isArray(cmds) ? cmds.join(',') : cmds, gmOnly);
(_.isArray(cmds) ? cmds : [cmds]).forEach(cmdString => (commands[cmdString] = command));
return command;
},

addModule(module) {
if (!(module instanceof ShapedModule)) {
throw new Error('Can only pass ShapedModules to addModule');
}
return module.configure(this);
},

const cp = {
processCommand(msg) {
const prefix = `!${rootCommand}-`;
if (msg.type === 'api' && msg.content.indexOf(prefix) === 0) {
Expand All @@ -309,6 +291,14 @@ module.exports = function commandParser(rootCommand, roll20, errorHandler) {
}
},

addCommand(cmds, handler, gmOnly) {
const command = new Command(this, handler, roll20, _.isArray(cmds) ? cmds.join(',') : cmds, gmOnly);
(_.isArray(cmds) ? cmds : [cmds]).forEach(cmdString => (commands[cmdString] = command));
return command;
},

logWrap: 'commandParser',
};
eventDispatcher.registerEventHandler('chat:message', cp.processCommand.bind(cp));
return cp;
};
77 changes: 73 additions & 4 deletions lib/entry-point.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,49 @@ const EntityLookup = require('./entity-lookup');
const JSONValidator = require('./json-validator');
const EntityLookupResultReporter = require('./entity-lookup-result-reporter');
const Reporter = require('./reporter');
const ShapedScripts = require('./shaped-script');
const makeCommandProc = require('./command-parser');
const AbilityMaker = require('./modules/ability-maker');
const ConfigUI = require('./modules/config-ui');
const AdvantageTracker = require('./modules/advantage-tracker');
const RestManager = require('./modules/rest-manager');
const UsesManager = require('./modules/uses-manager');
const AmmoManager = require('./modules/ammo-manager');
const Importer = require('./modules/importer');
const DeathSaveManager = require('./modules/death-save-manager');
const HDManager = require('./modules/hd-manager');
const FXManager = require('./modules/fx-manager');
const SpellManager = require('./modules/spell-manager');
const TokenBarConfigurer = require('./modules/token-bar-configurer');
const srdConverter = require('./srd-converter');
const UserError = require('./user-error');
const Migrator = require('./migrations');
const EventDispatcher = require('./event-dispatcher');
const ChatWatcher = require('./chat-watcher');
const utils = require('./utils');


const roll20 = new Roll20();
const myState = roll20.getState('ShapedScripts');
const logger = new Logger('5eShapedCompanion', roll20);
const el = new EntityLookup();
const reporter = new Reporter(roll20, 'Shaped Scripts');
const shaped = new ShapedScripts(logger, myState, roll20, parseModule.getParser(mmFormat, logger), el, reporter);

const errorHandler = function errorHandler(e) {
if (typeof e === 'string' || e instanceof parseModule.ParserError || e instanceof UserError) {
reporter.reportError(e);
logger.error('Error: $$$', e.toString());
}
else {
logger.error(e.toString());
logger.error(e.stack);
reporter.reportError('An error occurred. Please see the log for more details.');
}
};

const elrr = new EntityLookupResultReporter(logger, reporter);

const MINIMUM_SHEET_VERSION = '9.2.2';


roll20.logWrap = 'roll20';
logger.wrapModule(el);
Expand All @@ -29,8 +61,27 @@ el.configureEntity('monsters', [EntityLookup.jsonValidatorAsEntityProcessor(json
el.configureEntity('spells', [], EntityLookup.getVersionChecker('1.0.0', 'spells'));

roll20.on('ready', () => {
shaped.checkInstall();
shaped.registerEventHandlers();
logger.info('-=> ShapedScripts %%GULP_INJECT_VERSION%% <=-');
Migrator.migrateShapedConfig(myState, logger);
const character = roll20.createObj('character', { name: 'SHAPED_VERSION_TESTER' });
setTimeout(() => {
roll20.createAttrWithWorker(character.id, 'sheet_opened', 1, () => {
const version = roll20.getAttrByName(character.id, 'version');
setTimeout(() => {
character.remove();
}, 1000);
logger.info('Detected sheet version as : $$$', version);
if (utils.versionCompare(version, MINIMUM_SHEET_VERSION) < 0) {
reporter.reportError(`Incompatible sheet version. You need at least version ${MINIMUM_SHEET_VERSION} to use ` +
'this script.');
return;
}
const ed = new EventDispatcher(roll20, errorHandler, logger, reporter);
const cw = new ChatWatcher(roll20, logger, ed);
const commandProc = makeCommandProc('shaped', roll20, errorHandler, ed, version);
getModuleList().forEach(module => module.configure(roll20, reporter, logger, myState, commandProc, cw, ed));
});
}, 400);
});

module.exports = {
Expand All @@ -52,3 +103,21 @@ module.exports = {
}
},
};

function getModuleList() {
const abilityMaker = new AbilityMaker();
return [
abilityMaker,
new ConfigUI(),
new AdvantageTracker(),
new UsesManager(),
new RestManager(),
new AmmoManager(),
new Importer(el, parseModule.getParser(mmFormat, logger), abilityMaker, srdConverter),
new DeathSaveManager(),
new HDManager(),
new FXManager(),
new SpellManager(),
new TokenBarConfigurer(),
];
}
90 changes: 90 additions & 0 deletions lib/event-dispatcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict';
const _ = require('underscore');

module.exports = class EventDispatcher {

constructor(roll20, errorHandler, logger, reporter) {
this.roll20 = roll20;
this.addedTokenIds = [];
this.errorHandler = errorHandler;
this.logger = logger;
this.reporter = reporter;
this.addTokenListeners = [];
this.attributeChangeHandlers = {};
logger.wrapModule(this);
roll20.on('add:token', this.handleAddToken.bind(this));
roll20.on('change:token', this.handleChangeTokenForAdd.bind(this));
roll20.on('chat:message', (msg) => {
if (msg.playerid !== 'API') {
reporter.setPlayer(msg.playerid);
}
});
roll20.on('change:attribute', (curr, prev) => {
(this.attributeChangeHandlers[curr.get('name')] || []).forEach(handler => handler(curr, prev));
});
}

/////////////////////////////////////////////////
// Event Handlers
/////////////////////////////////////////////////
handleAddToken(token) {
const represents = token.get('represents');
if (_.isEmpty(represents)) {
return;
}
const character = this.roll20.getObj('character', represents);
if (!character) {
return;
}
this.addedTokenIds.push(token.id);

// URGH. Thanks Roll20.
setTimeout(() => {
const addedToken = this.roll20.getObj('graphic', token.id);
if (addedToken) {
this.handleChangeTokenForAdd(addedToken);
}
}, 100);
}

handleChangeTokenForAdd(token) {
if (_.contains(this.addedTokenIds, token.id)) {
this.addedTokenIds = _.without(this.addedTokenIds, token.id);
this.addTokenListeners.forEach(listener => listener(token));
// this.setTokenBarsOnDrop(token, true);
}
}

registerEventHandler(eventType, handler) {
if (eventType === 'add:token') {
this.addTokenListeners.push(this.wrapHandler(handler));
}
else {
this.roll20.on(eventType, this.wrapHandler(handler));
}
}

registerAttributeChangeHandler(attributeName, handler) {
this.attributeChangeHandlers[attributeName] = this.attributeChangeHandlers[attributeName] || [];
this.attributeChangeHandlers[attributeName].push(this.wrapHandler(handler));
}

wrapHandler(handler) {
const self = this;
return function handlerWrapper() {
try {
handler.apply(null, arguments);
}
catch (e) {
self.errorHandler(e);
}
finally {
self.logger.prefixString = '';
}
};
}

get logWrap() {
return 'EventDispatcher';
}
};
6 changes: 3 additions & 3 deletions lib/ability-maker.js → lib/modules/ability-maker.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
const _ = require('underscore');
const utils = require('./utils');
const ShapedModule = require('./shaped-module');
const ShapedConfig = require('./shaped-config');
const utils = require('./../utils');
const ShapedModule = require('./../shaped-module');
const ShapedConfig = require('./../shaped-config');

const RECHARGE_LOOKUP = {
TURN: '(T)',
Expand Down
25 changes: 21 additions & 4 deletions lib/advantage-tracker.js → lib/modules/advantage-tracker.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
const _ = require('underscore');
const utils = require('./utils.js');
const ShapedModule = require('./shaped-module');
const ShapedConfig = require('./shaped-config');
const utils = require('./../utils.js');
const ShapedModule = require('./../shaped-module');
const ShapedConfig = require('./../shaped-config');

const rollOptions = {
normal: {
Expand Down Expand Up @@ -41,6 +41,23 @@ class AdvantageTracker extends ShapedModule {
});
}

registerEventListeners(eventDispatcher) {
eventDispatcher.registerEventHandler('add:token', this.handleTokenAdded.bind(this));
eventDispatcher.registerAttributeChangeHandler('shaped_d20', this.handleRollOptionChange.bind(this));
}

registerChatListeners(chatWatcher) {
chatWatcher.registerChatListener(['character', '2d20kh1'], this.handleD20Roll.bind(this));
chatWatcher.registerChatListener(['character', '2d20kl1'], this.handleD20Roll.bind(this));
}

handleD20Roll(options) {
const autoRevertOptions = this.roll20.getAttrByName(options.character.id, 'auto_revert_advantage');
if (autoRevertOptions === 1 || autoRevertOptions === '1') {
this.setRollOption('normal', [options.character]);
}
}

process(options) {
let type;

Expand Down Expand Up @@ -104,7 +121,7 @@ class AdvantageTracker extends ShapedModule {
}
}

handleTokenChange(token) {
handleTokenAdded(token) {
this.logger.debug('AT: Updating New Token');
if (this.shouldShowMarkers() && token.get('represents') !== '') {
const character = this.roll20.getObj('character', token.get('represents'));
Expand Down
Loading

0 comments on commit 35ba538

Please sign in to comment.