diff --git a/app/imports/api/creature/computation/EffectAggregator.js b/app/imports/api/creature/computation/EffectAggregator.js index 7dadd06e..32d53af1 100644 --- a/app/imports/api/creature/computation/EffectAggregator.js +++ b/app/imports/api/creature/computation/EffectAggregator.js @@ -1,6 +1,13 @@ +import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js'; + export default class EffectAggregator{ - constructor(stat){ - this.base = stat.baseValue || 0; + constructor(stat, memo){ + if (stat.baseValueCalculation){ + this.statBaseValue = evaluateCalculation(stat.baseValueCalculation, memo); + this.base = +this.statBaseValue; + } else { + this.base = 0; + } this.add = 0; this.mul = 1; this.min = Number.NEGATIVE_INFINITY; @@ -15,47 +22,47 @@ export default class EffectAggregator{ addEffect(effect){ let result = effect.result; switch(effect.operation){ - case "base": + case 'base': // Take the largest base value this.base = result > this.base ? result : this.base; break; - case "add": + case 'add': // Add all adds together this.add += result; break; - case "mul": + case 'mul': // Multiply the muls together this.mul *= result; break; - case "min": + case 'min': // Take the largest min value this.min = result > this.min ? result : this.min; break; - case "max": + case 'max': // Take the smallest max value this.max = result < this.max ? result : this.max; break; - case "advantage": + case 'advantage': // Sum number of advantages this.advantage++; break; - case "disadvantage": + case 'disadvantage': // Sum number of disadvantages this.disadvantage++; break; - case "passiveAdd": + case 'passiveAdd': // Add all passive adds together this.passiveAdd += result; break; - case "fail": + case 'fail': // Sum number of fails this.fail++; break; - case "conditional": + case 'conditional': // Store array of conditionals this.conditional.push(result); break; - case "rollBonus": + case 'rollBonus': // Store array of roll bonuses this.rollBonus.push(result); break; diff --git a/app/imports/api/creature/computation/afterComputation/evaluateString.js b/app/imports/api/creature/computation/afterComputation/evaluateString.js index d149eb4a..c3823b00 100644 --- a/app/imports/api/creature/computation/afterComputation/evaluateString.js +++ b/app/imports/api/creature/computation/afterComputation/evaluateString.js @@ -1,4 +1,5 @@ import * as math from 'mathjs'; +import replaceBareSymbolsWithValueAccessor from '/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js'; export default function evaluateString(string, scope){ let errors = []; @@ -28,7 +29,7 @@ export default function evaluateString(string, scope){ } catch (e1){ errors.push(e1); try { - result = simplifyWithAccessors(calc, scope).toHTML(); + let result = simplifyWithAccessors(calc, scope).toHTML(); return {result, errors}; } catch (e2){ errors.push(e2); @@ -37,17 +38,6 @@ export default function evaluateString(string, scope){ } } -function replaceBareSymbolsWithValueAccessor(node, path, parent) { - if (node.isSymbolNode && path !== 'object') { - const object = new math.SymbolNode(node.name); - const address = new math.ConstantNode('value'); - const index = new math.IndexNode([address]); - return new math.AccessorNode(object, index); - } else { - return node; - } -} - function simplifyWithAccessors(calc, scope){ let noAccessorCalc = calc.transform(substituteAccessors(scope)); return math.simplify(noAccessorCalc); @@ -56,7 +46,7 @@ function simplifyWithAccessors(calc, scope){ // returns a function to replace all accessors with either their resolved value // or a symbol to simplify with function substituteAccessors(scope){ - return function(node, path, parent){ + return function(node){ if (node.isAccessorNode){ try { return evaluateAccessor(node, scope); @@ -84,6 +74,7 @@ function replaceAccessorWithSymbol(node){ return symbolNode; } +/* function overrideSymbolNodeHTML(symbolNode){ let safeName = escape(symbolNode.name); symbolNode.toHTML = function(){ @@ -105,3 +96,4 @@ function escape (value) { return text } +*/ diff --git a/app/imports/api/creature/computation/combineStat.js b/app/imports/api/creature/computation/combineStat.js index 7542c729..2592aa45 100644 --- a/app/imports/api/creature/computation/combineStat.js +++ b/app/imports/api/creature/computation/combineStat.js @@ -1,5 +1,4 @@ import computeStat from '/imports/api/creature/computation/computeStat.js'; -import computedValueOfVariableName from '/imports/api/creature/computation/computedValueOfVariableName.js' export default function combineStat(stat, aggregator, memo){ @@ -18,6 +17,7 @@ function combineAttribute(stat, aggregator){ if (result > aggregator.max) result = aggregator.max; if (!stat.decimal) result = Math.floor(result); stat.value = result; + stat.baseValue = aggregator.statBaseValue; if (stat.attributeType === 'ability') { stat.modifier = Math.floor((result - 10) / 2); } @@ -40,7 +40,9 @@ function combineSkill(stat, aggregator, memo){ if (prof.value > stat.proficiency) stat.proficiency = prof.value; } // Get the character's proficiency bonus to apply - let profBonus = computedValueOfVariableName('proficiencyBonus', memo); + let profBonusStat = memo.statsByVariableName['proficiencyBonus']; + let profBonus = profBonusStat && profBonusStat.value; + /** TODO level needs to be on the memo somewhere if (typeof profBonus !== "number"){ profBonus = Math.floor(char.level / 4 + 1.75); diff --git a/app/imports/api/creature/computation/computeStat.js b/app/imports/api/creature/computation/computeStat.js index f2dce516..8e8b579c 100644 --- a/app/imports/api/creature/computation/computeStat.js +++ b/app/imports/api/creature/computation/computeStat.js @@ -17,7 +17,7 @@ export default function computeStat(stat, memo){ return; } // Compute and aggregate all the effects - let aggregator = new EffectAggregator(stat) + let aggregator = new EffectAggregator(stat, memo) each(stat.computationDetails.effects, (effect) => { computeEffect(effect, memo); aggregator.addEffect(effect); diff --git a/app/imports/api/creature/computation/computedValueOfVariableName.js b/app/imports/api/creature/computation/computedValueOfVariableName.js deleted file mode 100644 index dc805983..00000000 --- a/app/imports/api/creature/computation/computedValueOfVariableName.js +++ /dev/null @@ -1,14 +0,0 @@ -import { get } from 'lodash'; -import computeStat from '/imports/api/creature/computation/computeStat.js'; - -export default function computedValueOfVariableName(name, memo){ - let path = name.split('.'); - let statName = path[0]; - let statPath = path.slice(1); - const stat = get(memo.statsByVariableName, statName); - if (!stat) return null; - if (!stat.computationDetails.computed){ - computeStat(stat, memo); - } - return statPath.length ? get(stat, statPath) : stat.value; -} diff --git a/app/imports/api/creature/computation/evaluateCalculation.js b/app/imports/api/creature/computation/evaluateCalculation.js index 2f729eeb..a39199bd 100644 --- a/app/imports/api/creature/computation/evaluateCalculation.js +++ b/app/imports/api/creature/computation/evaluateCalculation.js @@ -1,4 +1,5 @@ -import computedValueOfVariableName from '/imports/api/creature/computation/computedValueOfVariableName.js' +import replaceBareSymbolsWithValueAccessor from '/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js'; +import computeStat from '/imports/api/creature/computation/computeStat.js'; import * as math from 'mathjs'; export default function evaluateCalculation(string, memo){ @@ -11,20 +12,20 @@ export default function evaluateCalculation(string, memo){ console.error(e); return string; } - // Replace all symbols with known values - let substitutedCalc = calc.transform(node => { - if (node.isSymbolNode) { - let val = computedValueOfVariableName(node.name, memo); - if (val === null) return node; - return new math.ConstantNode(val); - } - else { - return node; + // Ensure all symbol nodes are defined and coputed + calc.traverse(node => { + if (node.isSymbolNode){ + let stat = memo.statsByVariableName[node.name]; + if (stat && !stat.computationDetails.computed){ + computeStat(stat, memo); + } } }); + // Ensure any bare symbols are value accessors instead + let substitutedCalc = calc.transform(replaceBareSymbolsWithValueAccessor); // Evaluate the expression to a number or return with substitutions try { - return substitutedCalc.evaluate(); + return substitutedCalc.evaluate(memo.statsByVariableName); } catch (e){ return substitutedCalc.toString(); } diff --git a/app/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js b/app/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js new file mode 100644 index 00000000..dbdd4f99 --- /dev/null +++ b/app/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js @@ -0,0 +1,12 @@ +import * as math from 'mathjs'; + +export default function replaceBareSymbolsWithValueAccessor(node, path) { + if (node.isSymbolNode && path !== 'object') { + const object = new math.SymbolNode(node.name); + const address = new math.ConstantNode('value'); + const index = new math.IndexNode([address]); + return new math.AccessorNode(object, index); + } else { + return node; + } +} diff --git a/app/imports/api/creature/computation/writeAlteredProperties.js b/app/imports/api/creature/computation/writeAlteredProperties.js index 3228812b..efd316b6 100644 --- a/app/imports/api/creature/computation/writeAlteredProperties.js +++ b/app/imports/api/creature/computation/writeAlteredProperties.js @@ -32,9 +32,13 @@ export default function writeAlteredProperties(memo){ if (!isEqual(original[key], changed[key])){ if (!op) op = newOperation(_id, changed.type); let value = changed[key]; - // Use null instead of undefined because it works with the $set operator - if (value === undefined) value = null; - op.updateOne.update.$set[key] = value; + if (value === undefined){ + // Unset values that become undefined + addUnsetOp(op, key); + } else { + // Set values that changed to something else + addSetOp(op, key, value); + } } } if (op){ @@ -48,7 +52,7 @@ function newOperation(_id, type){ let newOp = { updateOne: { filter: {_id}, - update: {'$set': {}}, + update: {}, } }; if (Meteor.isClient){ @@ -57,6 +61,22 @@ function newOperation(_id, type){ return newOp; } +function addSetOp(op, key, value){ + if (op.updateOne.update.$set){ + op.updateOne.update.$set[key] = value; + } else { + op.updateOne.update.$set = {[key]: value}; + } +} + +function addUnsetOp(op, key){ + if (op.updateOne.update.$unset){ + op.updateOne.update.$unset[key] = 1; + } else { + op.updateOne.update.$unset = {[key]: 1}; + } +} + function bulkWriteProperties(bulkWriteOps){ if (!bulkWriteOps.length) return; if (Meteor.isServer){ diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index 69246013..de72d92c 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -34,8 +34,8 @@ let AttributeSchema = new SimpleSchema({ index: 1, }, // The starting value, before effects - baseValue: { - type: Number, + baseValueCalculation: { + type: String, optional: true, }, // Description of what the attribute is used for @@ -63,6 +63,11 @@ let AttributeSchema = new SimpleSchema({ }); let ComputedOnlyAttributeSchema = new SimpleSchema({ + // The result of baseValueCalculation + baseValue: { + type: SimpleSchema.oneOf(Number, String, Boolean), + optional: true, + }, // The computed value of the attribute value: { type: SimpleSchema.oneOf(Number, String, Boolean), diff --git a/app/imports/ui/properties/forms/AttributeForm.vue b/app/imports/ui/properties/forms/AttributeForm.vue index efe05d90..6c6fbcce 100644 --- a/app/imports/ui/properties/forms/AttributeForm.vue +++ b/app/imports/ui/properties/forms/AttributeForm.vue @@ -3,13 +3,12 @@