diff --git a/app/.meteor/packages b/app/.meteor/packages index ef2e1a5b..0e84bba5 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -13,7 +13,6 @@ matb33:collection-hooks momentjs:moment dburles:mongo-collection-instances percolate:migrations -ecwyne:mathjs accounts-google@1.3.2 splendido:accounts-meld email@1.2.3 diff --git a/app/.meteor/versions b/app/.meteor/versions index 89aaa93f..b1c025bd 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -39,7 +39,6 @@ ecmascript@0.12.4 ecmascript-runtime@0.7.0 ecmascript-runtime-client@0.8.0 ecmascript-runtime-server@0.7.1 -ecwyne:mathjs@0.25.0 ejson@1.1.0 email@1.2.3 es5-shim@4.8.0 diff --git a/app/imports/api/creature/creatureComputation.js b/app/imports/api/creature/creatureComputation.js index eb352a06..f98291aa 100644 --- a/app/imports/api/creature/creatureComputation.js +++ b/app/imports/api/creature/creatureComputation.js @@ -3,7 +3,6 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; import SimpleSchema from 'simpl-schema'; -import schema from '/imports/api/schema.js'; import { canEditCreature } from '/imports/api/creature/creaturePermission.js'; import Creatures from "/imports/api/creature/Creatures.js"; import Attributes from "/imports/api/creature/properties/Attributes.js"; @@ -11,12 +10,13 @@ import Skills from "/imports/api/creature/properties/Skills.js"; import Effects from "/imports/api/creature/properties/Effects.js"; import DamageMultipliers from "/imports/api/creature/properties/DamageMultipliers.js"; import Classes from "/imports/api/creature/properties/Classes.js"; +import * as math from 'mathjs'; export const recomputeCreature = new ValidatedMethod({ name: "Creatures.methods.recomputeCreature", - validate: schema({ + validate: new SimpleSchema({ charId: { type: String } }).validator(), @@ -99,37 +99,9 @@ function writeCreature(char) { function writeCreatureDoc(char) { // Store all the variables, using the same priority as computation evaluation - // Attributes let variables = {}; - for (let key in char.atts){ - variables[key] = char.atts[key].result; - if ( - char.atts[key].attributeType === 'ability' && - !variables.hasOwnProperty(key + 'Mod') - ){ - variables[key + 'Mod'] = char.atts[key].mod; - } - } - for (let key in char.skills){ - if (!variables.hasOwnProperty(key)){ - variables[key] = char.skills[key].result; - } - } - // Damage Multipliers - for (let key in char.dms){ - if (!variables.hasOwnProperty(key)){ - variables[key] = char.dms[key].result; - } - } - // Class levels - for (let key in char.classes){ - if (!variables.hasOwnProperty(key + 'Level')){ - variables[key + 'Level'] = char.classes[key].level; - } - } - // Creature level - if (!variables.hasOwnProperty('level')){ - variables['level'] = char.level; + for (let key in char.variables){ + variables[key] = char.variables[key].result; } // Write the creature @@ -139,13 +111,6 @@ function writeCreatureDoc(char) { /* * Write all the attributes from the in-memory char object to the Attirbute docs */ - -/** - * writeAttributes - description - * - * @param {type} char description - * @returns {type} description - */ function writeAttributes(char) { let bulkWriteOps = _.map(char.atts, (att, variableName) => { let op = { @@ -261,7 +226,7 @@ function writeDamageMultipliers(char) { /** * Get the creature's data from the database and build an in-memory model that - * can be computed. Hits 6 database collections with indexed queries. + * can be computed. Hits 7 database collections with indexed queries. * * @param {type} charId description * @returns {type} description @@ -273,14 +238,16 @@ function buildCreature(charId){ skills: {}, dms: {}, classes: {}, + variables: {}, otherEffects: [], computedEffects: [], level: 0, }; // Fetch the attributes of the creature and add them to an object for quick lookup Attributes.find({charId}).forEach(attribute => { - if (!char.atts[attribute.variableName]){ - char.atts[attribute.variableName] = { + const key = attribute.variableName; + if (!char.atts[key]){ + char.atts[key] = { computed: false, busyComputing: false, type: "attribute", @@ -295,13 +262,27 @@ function buildCreature(charId){ max: Number.POSITIVE_INFINITY, effects: [], }; + char.variables[key] = char.atts[key]; + if (attribute.type === 'ability' && !char.variables[key + "Mod"]){ + char.variables[key + "Mod"] = { + type: "abilityMod", + ability: char.atts[key], + get result(){ + return this.ability.mod; + }, + get computed(){ + return this.ability.computed; + }, + } + } } }); // Fetch the skills of the creature and store them Skills.find({charId}).forEach(skill => { - if (!char.skills[skill.variableName]){ - char.skills[skill.variableName] = { + const key = skill.variableName; + if (!char.skills[key]){ + char.skills[key] = { computed: false, busyComputing: false, type: "skill", @@ -321,13 +302,17 @@ function buildCreature(charId){ effects: [], proficiencies: [], }; + if (!char.variables[key]){ + char.variables[key] = char.skills[key]; + } } }); // Fetch the damage multipliers of the creature and store them DamageMultipliers.find({charId}).forEach(damageMultiplier =>{ - if (!char.dms[damageMultiplier.variableName]){ - char.dms[damageMultiplier.variableName] = { + const key = damageMultiplier.variableName + if (!char.dms[key]){ + char.dms[key] = { computed: false, busyComputing: false, type: "damageMultiplier", @@ -337,19 +322,53 @@ function buildCreature(charId){ vulnerabilityCount: 0, effects: [], }; + if (!char.variables[key]){ + char.variables[key] = char.dms[key]; + } } }); // Fetch the class levels and store them // don't use the word "class" it's reserved + const levelOverwritten = !!char.variables['level'] + if (!levelOverwritten){ + char.variables['level'] = { + result: 0, + type: 'characterLevel', + computed: true, + }; + } Classes.find({charId}).forEach(cls => { const strippedCls = cls.name.replace(/\s+/g, ''); if (!char.classes[strippedCls]){ char.classes[strippedCls] = {level: cls.level}; char.level += cls.level; + if (!char.variables[strippedCls]){ + char.variables[strippedCls + "Level"] = { + result: cls.level, + type: 'classLevel', + computed: true, + }; + } + if (!levelOverwritten){ + char.variables['level'].result = char.level; + } } }); + // Add direct properties from creature to variable list + const fields = { xp: 1, weightCarried: 1}; + const creature = Creatures.findOne(charId, {fields}); + for (let key in fields){ + if (!char.variables[key]){ + char.variables[key] = { + result: creature[key] || 0, + type: 'creatureProperty', + computed: true, + }; + } + } + // Fetch the effects which apply to each stat and store them under the attribute Effects.find({ charId: charId, @@ -386,7 +405,6 @@ function buildCreature(charId){ return char; }; - /** * Compute the creature's stats in-place, returns the same char object * @param {type} char description @@ -422,6 +440,11 @@ export function computeCreature(char){ * @returns {type} description */ function computeStat(stat, char){ + // Ability mods aren't stats, use the stat they are based off of + if (stat.type === 'abilityMod'){ + stat = stat.ability; + } + // If the stat is already computed, skip it if (stat.computed) return; @@ -435,6 +458,7 @@ function computeStat(stat, char){ return; } + // Iterate over each effect which applies to the stat for (i in stat.effects){ computeEffect(stat.effects[i], char); @@ -451,11 +475,7 @@ function computeStat(stat, char){ } /** - * const computeEffect - Compute a single effect on a creature - * - * @param {Object} effect The effect to compute - * @param {Object} char The char document to compute with - * @returns {undefined} description + * Compute a the result of a single effect */ function computeEffect(effect, char){ if (effect.computed) return; @@ -465,80 +485,67 @@ function computeEffect(effect, char){ effect.result = effect.calculation; } else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){ effect.result = 1; - } else if (_.isString(effect.calculation)){ + } else { effect.result = evaluateCalculation(effect.calculation, char); } effect.computed = true; char.computedEffects.push(effect); }; - /** * Apply a computed effect to its stat - * - * @param {type} effect description - * @param {type} stat description - * @returns {type} description */ function applyEffect(effect, stat){ - // Take the largest base value - if (effect.operation === "base"){ - if (!_.has(stat, "base")) return; - stat.base = effect.result > stat.base ? effect.result : stat.base; + if (!_.has(stat, effect.operation)){ + return; } - // Add all adds together - else if (effect.operation === "add"){ - if (!_.has(stat, "add")) return; - stat.add += effect.result; - } - else if (effect.operation === "mul"){ - if (!_.has(stat, "mul")) return; - stat.mul *= effect.result; - } - // Take the largest min value - if (effect.operation === "min"){ - if (!_.has(stat, "min")) return; - stat.min = effect.result > stat.min ? effect.result : stat.min; - } - // Take the smallest max value - if (effect.operation === "max"){ - if (!_.has(stat, "max")) return; - stat.max = effect.result < stat.max ? effect.result : stat.max; - } - // Sum number of advantages - else if (effect.operation === "advantage"){ - if (!_.has(stat, "advantage")) return; - stat.advantage++; - } - // Sum number of disadvantages - else if (effect.operation === "disadvantage"){ - if (!_.has(stat, "disadvantage")) return; - stat.disadvantage++; - } - // Add all passive adds together - else if (effect.operation === "passiveAdd"){ - if (!_.has(stat, "passiveAdd")) return; - stat.passiveAdd += effect.result; - } - // Sum number of fails - else if (effect.operation === "fail"){ - if (!_.has(stat, "fail")) return; - stat.fail++; - } - // Sum number of conditionals - else if (effect.operation === "conditional"){ - if (!_.has(stat, "conditional")) return; - stat.conditional++; + switch(effect.operation){ + case "base": + // Take the largest base value + stat.base = effect.result > stat.base ? effect.result : stat.base; + break; + case "add": + // Add all adds together + stat.add += effect.result; + break; + case "mul": + // Multiply the muls together + stat.mul *= effect.result; + break; + case "min": + // Take the largest min value + stat.min = effect.result > stat.min ? effect.result : stat.min; + break; + case "max": + // Take the smallest max value + stat.max = effect.result < stat.max ? effect.result : stat.max; + break; + case "advantage": + // Sum number of advantages + stat.advantage++; + break; + case "disadvantage": + // Sum number of disadvantages + stat.disadvantage++; + break; + case "passiveAdd": + // Add all passive adds together + stat.passiveAdd += effect.result; + break; + case "fail": + // Sum number of fails + stat.fail++; + break; + case "conditional": + // Sum number of conditionals + stat.conditional++; + break; } }; /** * Combine the results of multiple effects to get the result of the stat - * - * @param {type} stat description - * @param {type} char description - * @returns {type} description */ function combineStat(stat, char){ if (stat.type === "attribute"){ @@ -553,16 +560,11 @@ function combineStat(stat, char){ /** * combineAttribute - Combine attributes's results into final values - * - * @param {type} stat description - * @param {type} char description - * @returns {type} description */ function combineAttribute(stat, char){ stat.result = (stat.base + stat.add) * stat.mul; if (stat.result < stat.min) stat.result = stat.min; if (stat.result > stat.max) stat.result = stat.max; - // Round everything that isn't the carry multiplier if (!stat.decimal) stat.result = Math.floor(stat.result); if (stat.attributeType === "ability") { stat.mod = Math.floor((stat.result - 10) / 2); @@ -571,11 +573,7 @@ function combineAttribute(stat, char){ /** - * combineSkill - Combine skills results into final values - * - * @param {type} stat description - * @param {type} char description - * @returns {type} description + * Combine skills results into final values */ function combineSkill(stat, char){ for (i in stat.proficiencies){ @@ -608,11 +606,7 @@ function combineSkill(stat, char){ } /** - * combineDamageMultiplier - Combine damageMultiplier's results into final values - * - * @param {type} stat description - * @param {type} char description - * @returns {type} description + * Combine damageMultiplier's results into final values */ function combineDamageMultiplier(stat, char){ if (stat.immunityCount) return 0; @@ -625,69 +619,47 @@ function combineDamageMultiplier(stat, char){ } } +/** + * Get the value of a key, compute it if necessary + */ +function getComputedValueOfKey(sub, char){ + const stat = char.variables[sub]; + if (!stat) return null; + if (!stat.computed){ + computeStat(stat, char); + } + return stat.result; +}; /** - * evaluateCalculation - Evaluate a string computation in the context of a char - * - * @param {type} string description - * @param {type} char description - * @returns {type} description + * Evaluate a string computation in the context of a char */ function evaluateCalculation(string, char){ if (!string) return string; - // Replace all the string variables with numbers if possible - string = string.replace(/\w*[a-z]\w*/gi, function(sub){ - // Attributes - if (char.atts[sub]){ - if (!char.atts[sub].computed){ - computeStat(char.atts[sub], char); - } - return char.atts[sub].result; + // Parse the string using mathjs + let calc; + try { + calc = math.parse(string); + } catch (e) { + return string; + } + // Replace all symbols with known values + let substitutedCalc = calc.transform(node => { + if (node.isSymbolNode) { + let val = getComputedValueOfKey(node.name, char); + if (val === null) return node; + return new math.expression.node.ConstantNode(val); } - // Modifiers - if (/^\w+mod$/i.test(sub)){ - var slice = sub.slice(0, -3); - if (char.atts[slice]){ - if (!char.atts[slice].computed){ - computeStat(char.atts[sub], char); - } - return char.atts[slice].mod; - } + else { + return node; } - // Skills - if (char.skills[sub]){ - if (!char.skills[sub].computed){ - computeStat(char.skills[sub], char); - } - return char.skills[sub].result; - } - // Damage Multipliers - if (char.dms[sub]){ - if (!char.dms[sub].computed){ - computeStat(char.dms[sub], char); - } - return char.dms[sub].result; - } - // Class levels - if (/^\w+levels?$/i.test(sub)){ - //strip out "level(s)" - var className = sub.replace(/levels?$/i, ""); - return char.classes[className] && char.classes[className].level; - } - // Creature level - if (sub === "level"){ - return char.level; - } - // Give up - return sub; }); - // Evaluate the expression to a number or return it as is. + // Evaluate the expression to a number or return with substitutions try { - var result = math.eval(string); // math.eval is safe - return result; + return substitutedCalc.eval(); } catch (e){ - return string; + return substitutedCalc.toString(); } }; @@ -699,7 +671,7 @@ function evaluateCalculation(string, char){ export const recomputeCreatureXP = new ValidatedMethod({ name: "Creatures.methods.recomputeCreatureXP", - validate: schema({ + validate: new SimpleSchema({ charId: { type: String } }).validator(), @@ -729,7 +701,7 @@ export const recomputeCreatureXP = new ValidatedMethod({ export const recomputeCreatureWeightCarried = new ValidatedMethod({ name: "Creature.methods.recomputeCreatureWeightCarried", - validate: schema({ + validate: new SimpleSchema({ charId: { type: String } }).validator(), diff --git a/app/lib/functions/evaluate.js b/app/lib/functions/evaluate.js deleted file mode 100644 index 83778e8a..00000000 --- a/app/lib/functions/evaluate.js +++ /dev/null @@ -1,104 +0,0 @@ -// if we want to add more functions, consider pulling out into its own file -(function() { - math.import({ - "if": function(pred, a, b) { - return (!!(pred)) ? a : b; - } - }); -})(); - -//evaluates a calculation string -evaluate = function(charId, string, opts){ - var spellListId = opts && opts.spellListId; - if (!string) return string; - string = string.replace(/\b[a-z,1-9]+\b/gi, function(sub){ - //fields - if (Schemas.Character.schema(sub)){ - return Characters.calculate.fieldValue(charId, sub); - } - //ability modifiers - var abilityMods = [ - "strengthMod", - "dexterityMod", - "constitutionMod", - "intelligenceMod", - "wisdomMod", - "charismaMod", - ]; - if (_.contains(abilityMods, sub)){ - var slice = sub.slice(0, -3); - try { - return Characters.calculate.abilityMod(charId, slice); - } catch (e){ - return sub; - } - } - //class levels - if (/\w+levels?\b/gi.test(sub)){ - //strip out "level" - var className = sub.replace(/levels?\b/gi, ""); - var cls = Classes.findOne({charId: charId, name: className}); - return cls && cls.level || sub; - } - //character level - if (sub.toUpperCase() === "LEVEL"){ - return Characters.calculate.level(charId); - } - if (spellListId && sub.toUpperCase() === "DC") { - var list = SpellLists.findOne(spellListId); - if (list && list.saveDC){ - return evaluate(charId, list.saveDC); - } - } - if (spellListId && sub.toUpperCase() === "ATTACKBONUS") { - var list = SpellLists.findOne(spellListId); - if (list && list.attackBonus){ - return evaluate(charId, list.attackBonus); - } - } - return sub; - }); - try { - var result = math.eval(string); - return result; - } catch (e){ - return string; - } -}; - -//takes a string with {calculations} and returns it with the results -//of the calculations returned in place -evaluateString = function(charId, string){ - //define brackets as curly brackets around anything that isn't a curly bracket - if (!string) return string; - var brackets = /\{[^\{\}]*\}/g; - var result = string.replace(brackets, function(exp){ - exp = exp.replace(/(\{|\})/g, ""); //remove curly brackets - return evaluate(charId, exp); - }); - return result; -}; - -evaluateSpellString = function (charId, spellListId, string) { - //define brackets as curly brackets around anything that isn't a curly bracket - if (!string) return string; - var brackets = /\{[^\{\}]*\}/g; - var result = string.replace(brackets, function(exp){ - exp = exp.replace(/(\{|\})/g, ""); //remove curly brackets - return evaluate(charId, exp, {spellListId}); - }); - return result; -} - -//returns the value of the effect if it exists, -//otherwise returns the result of the calculation if it exists, -//otherwise returns 0 -evaluateEffect = function(charId, effect){ - if (_.isFinite(effect.value)){ - return effect.value; - } else if (_.isString(effect.calculation)){ - return +evaluate(charId, effect.calculation); - } else { - return 0; - } -}; diff --git a/app/package-lock.json b/app/package-lock.json index b765c90a..ce57fb82 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -199,6 +199,11 @@ "delayed-stream": "~1.0.0" } }, + "complex.js": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", + "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -264,6 +269,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.2.tgz", + "integrity": "sha512-qL5tUTXAWjB5cSBfm0V2a4jO5FaDLumCfwc/0f7WaTOT3WU8pIeq2HHrd98eXHtbey4qFWlaPzfml1JWIoO9TQ==" + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -301,6 +311,11 @@ "once": "^1.4.0" } }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -363,6 +378,11 @@ "mime-types": "^2.1.12" } }, + "fraction.js": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz", + "integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -529,6 +549,11 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -795,6 +820,21 @@ "p-defer": "^1.0.0" } }, + "mathjs": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-5.5.0.tgz", + "integrity": "sha512-qvCNNcpPFpk85EYFiY9+jyWClFHBNqQlUQ3zKGy/EuLSicuJCmxEtHEQxThL0macmbgl1dzmenrgDbRpGE3QRg==", + "requires": { + "complex.js": "2.0.11", + "decimal.js": "10.0.2", + "escape-latex": "1.2.0", + "fraction.js": "4.0.12", + "javascript-natural-sort": "0.7.1", + "seed-random": "2.2.0", + "tiny-emitter": "2.1.0", + "typed-function": "1.1.0" + } + }, "mem": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", @@ -1406,6 +1446,37 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "stream-http": { @@ -1418,6 +1489,37 @@ "readable-stream": "^2.3.3", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "string_decoder": { @@ -1857,6 +1959,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" + }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", @@ -1997,6 +2104,11 @@ "uid-number": "^0.0.6" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -2026,6 +2138,11 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "typed-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.0.tgz", + "integrity": "sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA==" + }, "uid-number": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", diff --git a/app/package.json b/app/package.json index 74e2b7ac..a054b6b8 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,7 @@ "css-box-shadow": "^1.0.0-3", "fibers": "^2.0.2", "lodash": "^4.17.11", + "mathjs": "^5.5.0", "meteor-node-stubs": "^0.3.3", "qrcode": "^1.3.3", "simpl-schema": "^1.5.5",