Changed creature computations to leverage the MathJS parser (big deal)
This commit is contained in:
@@ -13,7 +13,6 @@ matb33:collection-hooks
|
|||||||
momentjs:moment
|
momentjs:moment
|
||||||
dburles:mongo-collection-instances
|
dburles:mongo-collection-instances
|
||||||
percolate:migrations
|
percolate:migrations
|
||||||
ecwyne:mathjs
|
|
||||||
accounts-google@1.3.2
|
accounts-google@1.3.2
|
||||||
splendido:accounts-meld
|
splendido:accounts-meld
|
||||||
email@1.2.3
|
email@1.2.3
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ ecmascript@0.12.4
|
|||||||
ecmascript-runtime@0.7.0
|
ecmascript-runtime@0.7.0
|
||||||
ecmascript-runtime-client@0.8.0
|
ecmascript-runtime-client@0.8.0
|
||||||
ecmascript-runtime-server@0.7.1
|
ecmascript-runtime-server@0.7.1
|
||||||
ecwyne:mathjs@0.25.0
|
|
||||||
ejson@1.1.0
|
ejson@1.1.0
|
||||||
email@1.2.3
|
email@1.2.3
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import schema from '/imports/api/schema.js';
|
|
||||||
import { canEditCreature } from '/imports/api/creature/creaturePermission.js';
|
import { canEditCreature } from '/imports/api/creature/creaturePermission.js';
|
||||||
import Creatures from "/imports/api/creature/Creatures.js";
|
import Creatures from "/imports/api/creature/Creatures.js";
|
||||||
import Attributes from "/imports/api/creature/properties/Attributes.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 Effects from "/imports/api/creature/properties/Effects.js";
|
||||||
import DamageMultipliers from "/imports/api/creature/properties/DamageMultipliers.js";
|
import DamageMultipliers from "/imports/api/creature/properties/DamageMultipliers.js";
|
||||||
import Classes from "/imports/api/creature/properties/Classes.js";
|
import Classes from "/imports/api/creature/properties/Classes.js";
|
||||||
|
import * as math from 'mathjs';
|
||||||
|
|
||||||
export const recomputeCreature = new ValidatedMethod({
|
export const recomputeCreature = new ValidatedMethod({
|
||||||
|
|
||||||
name: "Creatures.methods.recomputeCreature",
|
name: "Creatures.methods.recomputeCreature",
|
||||||
|
|
||||||
validate: schema({
|
validate: new SimpleSchema({
|
||||||
charId: { type: String }
|
charId: { type: String }
|
||||||
}).validator(),
|
}).validator(),
|
||||||
|
|
||||||
@@ -99,37 +99,9 @@ function writeCreature(char) {
|
|||||||
|
|
||||||
function writeCreatureDoc(char) {
|
function writeCreatureDoc(char) {
|
||||||
// Store all the variables, using the same priority as computation evaluation
|
// Store all the variables, using the same priority as computation evaluation
|
||||||
// Attributes
|
|
||||||
let variables = {};
|
let variables = {};
|
||||||
for (let key in char.atts){
|
for (let key in char.variables){
|
||||||
variables[key] = char.atts[key].result;
|
variables[key] = char.variables[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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the creature
|
// Write the creature
|
||||||
@@ -139,13 +111,6 @@ function writeCreatureDoc(char) {
|
|||||||
/*
|
/*
|
||||||
* Write all the attributes from the in-memory char object to the Attirbute docs
|
* 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) {
|
function writeAttributes(char) {
|
||||||
let bulkWriteOps = _.map(char.atts, (att, variableName) => {
|
let bulkWriteOps = _.map(char.atts, (att, variableName) => {
|
||||||
let op = {
|
let op = {
|
||||||
@@ -261,7 +226,7 @@ function writeDamageMultipliers(char) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the creature's data from the database and build an in-memory model that
|
* 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
|
* @param {type} charId description
|
||||||
* @returns {type} description
|
* @returns {type} description
|
||||||
@@ -273,14 +238,16 @@ function buildCreature(charId){
|
|||||||
skills: {},
|
skills: {},
|
||||||
dms: {},
|
dms: {},
|
||||||
classes: {},
|
classes: {},
|
||||||
|
variables: {},
|
||||||
otherEffects: [],
|
otherEffects: [],
|
||||||
computedEffects: [],
|
computedEffects: [],
|
||||||
level: 0,
|
level: 0,
|
||||||
};
|
};
|
||||||
// Fetch the attributes of the creature and add them to an object for quick lookup
|
// Fetch the attributes of the creature and add them to an object for quick lookup
|
||||||
Attributes.find({charId}).forEach(attribute => {
|
Attributes.find({charId}).forEach(attribute => {
|
||||||
if (!char.atts[attribute.variableName]){
|
const key = attribute.variableName;
|
||||||
char.atts[attribute.variableName] = {
|
if (!char.atts[key]){
|
||||||
|
char.atts[key] = {
|
||||||
computed: false,
|
computed: false,
|
||||||
busyComputing: false,
|
busyComputing: false,
|
||||||
type: "attribute",
|
type: "attribute",
|
||||||
@@ -295,13 +262,27 @@ function buildCreature(charId){
|
|||||||
max: Number.POSITIVE_INFINITY,
|
max: Number.POSITIVE_INFINITY,
|
||||||
effects: [],
|
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
|
// Fetch the skills of the creature and store them
|
||||||
Skills.find({charId}).forEach(skill => {
|
Skills.find({charId}).forEach(skill => {
|
||||||
if (!char.skills[skill.variableName]){
|
const key = skill.variableName;
|
||||||
char.skills[skill.variableName] = {
|
if (!char.skills[key]){
|
||||||
|
char.skills[key] = {
|
||||||
computed: false,
|
computed: false,
|
||||||
busyComputing: false,
|
busyComputing: false,
|
||||||
type: "skill",
|
type: "skill",
|
||||||
@@ -321,13 +302,17 @@ function buildCreature(charId){
|
|||||||
effects: [],
|
effects: [],
|
||||||
proficiencies: [],
|
proficiencies: [],
|
||||||
};
|
};
|
||||||
|
if (!char.variables[key]){
|
||||||
|
char.variables[key] = char.skills[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the damage multipliers of the creature and store them
|
// Fetch the damage multipliers of the creature and store them
|
||||||
DamageMultipliers.find({charId}).forEach(damageMultiplier =>{
|
DamageMultipliers.find({charId}).forEach(damageMultiplier =>{
|
||||||
if (!char.dms[damageMultiplier.variableName]){
|
const key = damageMultiplier.variableName
|
||||||
char.dms[damageMultiplier.variableName] = {
|
if (!char.dms[key]){
|
||||||
|
char.dms[key] = {
|
||||||
computed: false,
|
computed: false,
|
||||||
busyComputing: false,
|
busyComputing: false,
|
||||||
type: "damageMultiplier",
|
type: "damageMultiplier",
|
||||||
@@ -337,19 +322,53 @@ function buildCreature(charId){
|
|||||||
vulnerabilityCount: 0,
|
vulnerabilityCount: 0,
|
||||||
effects: [],
|
effects: [],
|
||||||
};
|
};
|
||||||
|
if (!char.variables[key]){
|
||||||
|
char.variables[key] = char.dms[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the class levels and store them
|
// Fetch the class levels and store them
|
||||||
// don't use the word "class" it's reserved
|
// 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 => {
|
Classes.find({charId}).forEach(cls => {
|
||||||
const strippedCls = cls.name.replace(/\s+/g, '');
|
const strippedCls = cls.name.replace(/\s+/g, '');
|
||||||
if (!char.classes[strippedCls]){
|
if (!char.classes[strippedCls]){
|
||||||
char.classes[strippedCls] = {level: cls.level};
|
char.classes[strippedCls] = {level: cls.level};
|
||||||
char.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
|
// Fetch the effects which apply to each stat and store them under the attribute
|
||||||
Effects.find({
|
Effects.find({
|
||||||
charId: charId,
|
charId: charId,
|
||||||
@@ -386,7 +405,6 @@ function buildCreature(charId){
|
|||||||
return char;
|
return char;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the creature's stats in-place, returns the same char object
|
* Compute the creature's stats in-place, returns the same char object
|
||||||
* @param {type} char description
|
* @param {type} char description
|
||||||
@@ -422,6 +440,11 @@ export function computeCreature(char){
|
|||||||
* @returns {type} description
|
* @returns {type} description
|
||||||
*/
|
*/
|
||||||
function computeStat(stat, char){
|
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 the stat is already computed, skip it
|
||||||
if (stat.computed) return;
|
if (stat.computed) return;
|
||||||
|
|
||||||
@@ -435,6 +458,7 @@ function computeStat(stat, char){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Iterate over each effect which applies to the stat
|
// Iterate over each effect which applies to the stat
|
||||||
for (i in stat.effects){
|
for (i in stat.effects){
|
||||||
computeEffect(stat.effects[i], char);
|
computeEffect(stat.effects[i], char);
|
||||||
@@ -451,11 +475,7 @@ function computeStat(stat, char){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* const computeEffect - Compute a single effect on a creature
|
* Compute a the result of a single effect
|
||||||
*
|
|
||||||
* @param {Object} effect The effect to compute
|
|
||||||
* @param {Object} char The char document to compute with
|
|
||||||
* @returns {undefined} description
|
|
||||||
*/
|
*/
|
||||||
function computeEffect(effect, char){
|
function computeEffect(effect, char){
|
||||||
if (effect.computed) return;
|
if (effect.computed) return;
|
||||||
@@ -465,80 +485,67 @@ function computeEffect(effect, char){
|
|||||||
effect.result = effect.calculation;
|
effect.result = effect.calculation;
|
||||||
} else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){
|
} else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){
|
||||||
effect.result = 1;
|
effect.result = 1;
|
||||||
} else if (_.isString(effect.calculation)){
|
} else {
|
||||||
effect.result = evaluateCalculation(effect.calculation, char);
|
effect.result = evaluateCalculation(effect.calculation, char);
|
||||||
}
|
}
|
||||||
effect.computed = true;
|
effect.computed = true;
|
||||||
char.computedEffects.push(effect);
|
char.computedEffects.push(effect);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a computed effect to its stat
|
* Apply a computed effect to its stat
|
||||||
*
|
|
||||||
* @param {type} effect description
|
|
||||||
* @param {type} stat description
|
|
||||||
* @returns {type} description
|
|
||||||
*/
|
*/
|
||||||
function applyEffect(effect, stat){
|
function applyEffect(effect, stat){
|
||||||
// Take the largest base value
|
if (!_.has(stat, effect.operation)){
|
||||||
if (effect.operation === "base"){
|
return;
|
||||||
if (!_.has(stat, "base")) return;
|
|
||||||
stat.base = effect.result > stat.base ? effect.result : stat.base;
|
|
||||||
}
|
}
|
||||||
// Add all adds together
|
switch(effect.operation){
|
||||||
else if (effect.operation === "add"){
|
case "base":
|
||||||
if (!_.has(stat, "add")) return;
|
// Take the largest base value
|
||||||
stat.add += effect.result;
|
stat.base = effect.result > stat.base ? effect.result : stat.base;
|
||||||
}
|
break;
|
||||||
else if (effect.operation === "mul"){
|
case "add":
|
||||||
if (!_.has(stat, "mul")) return;
|
// Add all adds together
|
||||||
stat.mul *= effect.result;
|
stat.add += effect.result;
|
||||||
}
|
break;
|
||||||
// Take the largest min value
|
case "mul":
|
||||||
if (effect.operation === "min"){
|
// Multiply the muls together
|
||||||
if (!_.has(stat, "min")) return;
|
stat.mul *= effect.result;
|
||||||
stat.min = effect.result > stat.min ? effect.result : stat.min;
|
break;
|
||||||
}
|
case "min":
|
||||||
// Take the smallest max value
|
// Take the largest min value
|
||||||
if (effect.operation === "max"){
|
stat.min = effect.result > stat.min ? effect.result : stat.min;
|
||||||
if (!_.has(stat, "max")) return;
|
break;
|
||||||
stat.max = effect.result < stat.max ? effect.result : stat.max;
|
case "max":
|
||||||
}
|
// Take the smallest max value
|
||||||
// Sum number of advantages
|
stat.max = effect.result < stat.max ? effect.result : stat.max;
|
||||||
else if (effect.operation === "advantage"){
|
break;
|
||||||
if (!_.has(stat, "advantage")) return;
|
case "advantage":
|
||||||
stat.advantage++;
|
// Sum number of advantages
|
||||||
}
|
stat.advantage++;
|
||||||
// Sum number of disadvantages
|
break;
|
||||||
else if (effect.operation === "disadvantage"){
|
case "disadvantage":
|
||||||
if (!_.has(stat, "disadvantage")) return;
|
// Sum number of disadvantages
|
||||||
stat.disadvantage++;
|
stat.disadvantage++;
|
||||||
}
|
break;
|
||||||
// Add all passive adds together
|
case "passiveAdd":
|
||||||
else if (effect.operation === "passiveAdd"){
|
// Add all passive adds together
|
||||||
if (!_.has(stat, "passiveAdd")) return;
|
stat.passiveAdd += effect.result;
|
||||||
stat.passiveAdd += effect.result;
|
break;
|
||||||
}
|
case "fail":
|
||||||
// Sum number of fails
|
// Sum number of fails
|
||||||
else if (effect.operation === "fail"){
|
stat.fail++;
|
||||||
if (!_.has(stat, "fail")) return;
|
break;
|
||||||
stat.fail++;
|
case "conditional":
|
||||||
}
|
// Sum number of conditionals
|
||||||
// Sum number of conditionals
|
stat.conditional++;
|
||||||
else if (effect.operation === "conditional"){
|
break;
|
||||||
if (!_.has(stat, "conditional")) return;
|
|
||||||
stat.conditional++;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine the results of multiple effects to get the result of the stat
|
* 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){
|
function combineStat(stat, char){
|
||||||
if (stat.type === "attribute"){
|
if (stat.type === "attribute"){
|
||||||
@@ -553,16 +560,11 @@ function combineStat(stat, char){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* combineAttribute - Combine attributes's results into final values
|
* combineAttribute - Combine attributes's results into final values
|
||||||
*
|
|
||||||
* @param {type} stat description
|
|
||||||
* @param {type} char description
|
|
||||||
* @returns {type} description
|
|
||||||
*/
|
*/
|
||||||
function combineAttribute(stat, char){
|
function combineAttribute(stat, char){
|
||||||
stat.result = (stat.base + stat.add) * stat.mul;
|
stat.result = (stat.base + stat.add) * stat.mul;
|
||||||
if (stat.result < stat.min) stat.result = stat.min;
|
if (stat.result < stat.min) stat.result = stat.min;
|
||||||
if (stat.result > stat.max) stat.result = stat.max;
|
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.decimal) stat.result = Math.floor(stat.result);
|
||||||
if (stat.attributeType === "ability") {
|
if (stat.attributeType === "ability") {
|
||||||
stat.mod = Math.floor((stat.result - 10) / 2);
|
stat.mod = Math.floor((stat.result - 10) / 2);
|
||||||
@@ -571,11 +573,7 @@ function combineAttribute(stat, char){
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* combineSkill - Combine skills results into final values
|
* Combine skills results into final values
|
||||||
*
|
|
||||||
* @param {type} stat description
|
|
||||||
* @param {type} char description
|
|
||||||
* @returns {type} description
|
|
||||||
*/
|
*/
|
||||||
function combineSkill(stat, char){
|
function combineSkill(stat, char){
|
||||||
for (i in stat.proficiencies){
|
for (i in stat.proficiencies){
|
||||||
@@ -608,11 +606,7 @@ function combineSkill(stat, char){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* combineDamageMultiplier - Combine damageMultiplier's results into final values
|
* Combine damageMultiplier's results into final values
|
||||||
*
|
|
||||||
* @param {type} stat description
|
|
||||||
* @param {type} char description
|
|
||||||
* @returns {type} description
|
|
||||||
*/
|
*/
|
||||||
function combineDamageMultiplier(stat, char){
|
function combineDamageMultiplier(stat, char){
|
||||||
if (stat.immunityCount) return 0;
|
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
|
* Evaluate a string computation in the context of a char
|
||||||
*
|
|
||||||
* @param {type} string description
|
|
||||||
* @param {type} char description
|
|
||||||
* @returns {type} description
|
|
||||||
*/
|
*/
|
||||||
function evaluateCalculation(string, char){
|
function evaluateCalculation(string, char){
|
||||||
if (!string) return string;
|
if (!string) return string;
|
||||||
// Replace all the string variables with numbers if possible
|
// Parse the string using mathjs
|
||||||
string = string.replace(/\w*[a-z]\w*/gi, function(sub){
|
let calc;
|
||||||
// Attributes
|
try {
|
||||||
if (char.atts[sub]){
|
calc = math.parse(string);
|
||||||
if (!char.atts[sub].computed){
|
} catch (e) {
|
||||||
computeStat(char.atts[sub], char);
|
return string;
|
||||||
}
|
}
|
||||||
return char.atts[sub].result;
|
// 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
|
else {
|
||||||
if (/^\w+mod$/i.test(sub)){
|
return node;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 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 {
|
try {
|
||||||
var result = math.eval(string); // math.eval is safe
|
return substitutedCalc.eval();
|
||||||
return result;
|
|
||||||
} catch (e){
|
} catch (e){
|
||||||
return string;
|
return substitutedCalc.toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -699,7 +671,7 @@ function evaluateCalculation(string, char){
|
|||||||
export const recomputeCreatureXP = new ValidatedMethod({
|
export const recomputeCreatureXP = new ValidatedMethod({
|
||||||
name: "Creatures.methods.recomputeCreatureXP",
|
name: "Creatures.methods.recomputeCreatureXP",
|
||||||
|
|
||||||
validate: schema({
|
validate: new SimpleSchema({
|
||||||
charId: { type: String }
|
charId: { type: String }
|
||||||
}).validator(),
|
}).validator(),
|
||||||
|
|
||||||
@@ -729,7 +701,7 @@ export const recomputeCreatureXP = new ValidatedMethod({
|
|||||||
export const recomputeCreatureWeightCarried = new ValidatedMethod({
|
export const recomputeCreatureWeightCarried = new ValidatedMethod({
|
||||||
name: "Creature.methods.recomputeCreatureWeightCarried",
|
name: "Creature.methods.recomputeCreatureWeightCarried",
|
||||||
|
|
||||||
validate: schema({
|
validate: new SimpleSchema({
|
||||||
charId: { type: String }
|
charId: { type: String }
|
||||||
}).validator(),
|
}).validator(),
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
117
app/package-lock.json
generated
117
app/package-lock.json
generated
@@ -199,6 +199,11 @@
|
|||||||
"delayed-stream": "~1.0.0"
|
"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": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
"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": {
|
"deep-extend": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
@@ -301,6 +311,11 @@
|
|||||||
"once": "^1.4.0"
|
"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": {
|
"execa": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
|
||||||
@@ -363,6 +378,11 @@
|
|||||||
"mime-types": "^2.1.12"
|
"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": {
|
"fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
"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": {
|
"jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
@@ -795,6 +820,21 @@
|
|||||||
"p-defer": "^1.0.0"
|
"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": {
|
"mem": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz",
|
||||||
@@ -1406,6 +1446,37 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "~2.0.1",
|
"inherits": "~2.0.1",
|
||||||
"readable-stream": "^2.0.2"
|
"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": {
|
"stream-http": {
|
||||||
@@ -1418,6 +1489,37 @@
|
|||||||
"readable-stream": "^2.3.3",
|
"readable-stream": "^2.3.3",
|
||||||
"to-arraybuffer": "^1.0.0",
|
"to-arraybuffer": "^1.0.0",
|
||||||
"xtend": "^4.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": {
|
"string_decoder": {
|
||||||
@@ -1857,6 +1959,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"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": {
|
"semver": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||||
@@ -1997,6 +2104,11 @@
|
|||||||
"uid-number": "^0.0.6"
|
"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": {
|
"tough-cookie": {
|
||||||
"version": "2.4.3",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
"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": {
|
"uid-number": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"css-box-shadow": "^1.0.0-3",
|
"css-box-shadow": "^1.0.0-3",
|
||||||
"fibers": "^2.0.2",
|
"fibers": "^2.0.2",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
|
"mathjs": "^5.5.0",
|
||||||
"meteor-node-stubs": "^0.3.3",
|
"meteor-node-stubs": "^0.3.3",
|
||||||
"qrcode": "^1.3.3",
|
"qrcode": "^1.3.3",
|
||||||
"simpl-schema": "^1.5.5",
|
"simpl-schema": "^1.5.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user