diff --git a/lib/command-parser.js b/lib/command-parser.js index 28da4d7..ca1c129 100644 --- a/lib/command-parser.js +++ b/lib/command-parser.js @@ -142,7 +142,7 @@ class Command { return this; } - handle(args, selection, cmdString, playerIsGM, playerId) { + handle(args, selection, cmdString, playerIsGM, playerId, requiredCharVersion) { if (!playerIsGM && this.gmOnly) { throw new UserError('You must be a GM to run this command'); } @@ -157,7 +157,8 @@ class Command { }, }); - startOptions.selected = this.selectionSpec && processSelection(selection || [], this.selectionSpec, this.roll20); + startOptions.selected = + this.selectionSpec && processSelection(selection || [], this.selectionSpec, this.roll20, requiredCharVersion); const finalOptions = _.chain(args) .reduce((options, arg) => { if (!_.some(this.parsers, parser => parser(arg, options.errors, options))) { @@ -224,14 +225,23 @@ class Command { } } -function processSelection(selection, constraints, roll20) { +function processSelection(selection, constraints, roll20, requiredCharVersion) { return _.reduce(constraints, (result, constraintDetails, type) => { const objects = _.chain(selection) .where({ _type: type === 'character' ? 'graphic' : type }) .map(selected => roll20.getObj(selected._type, selected._id)) .map((object) => { if (type === 'character' && object) { - return roll20.getObj('character', object.get('represents')); + const char = roll20.getObj('character', object.get('represents')); + if (!constraintDetails.anyVersion) { + const version = roll20.getAttrByName(char.id, 'version'); + if (version !== requiredCharVersion) { + throw new UserError(`Character ${char.get('name')} is not at the required sheet version ` + + `[${requiredCharVersion}], but instead [${version}]. Try opening the character sheet or running ` + + '!shaped-update-character to update it.'); + } + } + return char; } return object; }) @@ -262,7 +272,12 @@ function processSelection(selection, constraints, roll20) { module.exports = function commandParser(rootCommand, roll20, errorHandler) { 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)); @@ -286,8 +301,8 @@ module.exports = function commandParser(rootCommand, roll20, errorHandler) { if (!cmd) { throw new UserError(`Unrecognised command ${prefix}${cmdName}`); } - const returnVal = - cmd.handle(parts, msg.selected, `${prefix}${cmdName}`, roll20.playerIsGM(msg.playerid), msg.playerid); + const returnVal = cmd.handle(parts, msg.selected, `${prefix}${cmdName}`, + roll20.playerIsGM(msg.playerid), msg.playerid, requiredCharVersion); if (returnVal instanceof Promise) { returnVal.catch(errorHandler); } diff --git a/lib/importer.js b/lib/importer.js index 8f73b70..fffa344 100644 --- a/lib/importer.js +++ b/lib/importer.js @@ -81,6 +81,7 @@ class Importer extends ShapedModule { character: { min: 0, max: Infinity, + anyVersion: true, }, }) .option('all', ShapedConfig.booleanValidator) diff --git a/lib/shaped-script.js b/lib/shaped-script.js index fbfe3de..6c780f3 100644 --- a/lib/shaped-script.js +++ b/lib/shaped-script.js @@ -447,6 +447,15 @@ function ShapedScripts(logger, myState, roll20, parser, entityLookup, reporter) this.checkInstall = function checkInstall() { 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'); + character.remove(); + commandProc.setRequiredCharacterVersion(version); + logger.info('Detected sheet version as : $$$', version); + }); + }, 400); }; this.wrapHandler = function wrapHandler(handler) { diff --git a/test/test-ability-maker.js b/test/test-ability-maker.js new file mode 100644 index 0000000..6700d45 --- /dev/null +++ b/test/test-ability-maker.js @@ -0,0 +1,43 @@ +/* globals describe: false, it:false, beforeEach:false, before:false */ +'use strict'; +const Roll20 = require('roll20-wrapper'); +const expect = require('chai').expect; +const AbilityMaker = require('../lib/ability-maker'); +const sinon = require('sinon'); +const logger = require('./dummy-logger'); +const Reporter = require('./dummy-reporter'); +const cp = require('./dummy-command-parser'); +const Roll20Object = require('./dummy-roll20-object'); + +describe('ability-maker', function () { + let roll20; + + beforeEach(function () { + roll20 = new Roll20(); + }); + + + describe('ability creation', function () { + it('should create save ability', function () { + sinon.stub(roll20); + const characterStub = new Roll20Object('character'); + characterStub.set('name', 'Bob'); + const abilityStub = new Roll20Object('ability'); + roll20.getOrCreateObj.withArgs('ability', { + characterid: characterStub.id, + name: 'Saves', + }).returns(abilityStub); + const abilityMaker = new AbilityMaker(); + abilityMaker.configure(roll20, new Reporter(), logger, {}, cp); + abilityMaker.addAbility({ + selected: { character: [characterStub] }, + abilities: [abilityMaker.staticAbilityOptions.saves], + }); + + expect(roll20.getOrCreateObj.withArgs('ability', { + characterid: characterStub.id, + name: 'Saves', + }).callCount).to.equal(1); + }); + }); +}); diff --git a/test/test-shaped-script.js b/test/test-shaped-script.js index 4994cbe..373e023 100644 --- a/test/test-shaped-script.js +++ b/test/test-shaped-script.js @@ -58,35 +58,6 @@ describe('shaped-script', function () { }); }); - describe('ability creation', function () { - it('should create save ability', function () { - sinon.stub(roll20); - const characterStub = new Roll20Object('character'); - characterStub.set('name', 'Bob'); - const tokenStub = new Roll20Object('graphic'); - const abilityStub = new Roll20Object('ability'); - tokenStub.set('represents', characterStub.id); - roll20.getObj.withArgs('graphic', tokenStub.id).returns(tokenStub); - roll20.getObj.withArgs('character', characterStub.id).returns(characterStub); - roll20.getOrCreateObj.withArgs('ability', { - characterid: characterStub.id, - name: 'Saves', - }).returns(abilityStub); - const reporter = new Reporter(); - const shapedScript = new ShapedScripts(logger, { config: { updateAmmo: true } }, roll20, null, - el.entityLookup, reporter); - shapedScript.handleInput({ - type: 'api', - content: '!shaped-abilities --saves', - selected: [{ _type: 'graphic', _id: tokenStub.id }], - }); - expect(roll20.getOrCreateObj.withArgs('ability', { - characterid: characterStub.id, - name: 'Saves', - }).callCount).to.equal(1); - }); - }); - describe('handleSpellCast', function () { it('should deal with cantrips correctly', function () { const mock = sinon.mock(roll20);