From 2981813751ce317ca163270deb111ce32f495d28 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 23 Mar 2020 11:59:04 +0200 Subject: [PATCH] Creature computations working again --- .../api/creature/CreatureProperties.js | 2 +- .../creature/computation/ComputationMemo.js | 1 + .../api/creature/computation/combineStat.js | 7 +- .../api/creature/computation/computeEffect.js | 4 +- .../api/creature/computation/computeStat.js | 1 + .../computedValueOfVariableName.js | 4 +- .../computation/evaluateCalculation.js | 3 +- .../creature/computation/logAlterations.js | 16 --- ...ureComputation.js => recomputeCreature.js} | 11 +-- .../computation/writeAlteredProperties.js | 71 ++++++++++++++ .../api/creature/computation/writeCreature.js | 98 ------------------- .../api/creature/creatureComputation.test.js | 2 +- .../creature/mixins/recomputeCreatureMixin.js | 2 +- app/imports/api/parenting/parenting.js | 7 -- app/imports/api/properties/Attributes.js | 10 +- .../api/properties/DamageMultipliers.js | 1 - app/imports/api/properties/Effects.js | 10 +- app/imports/api/properties/Skills.js | 8 +- .../ui/creature/character/CharacterSheet.vue | 2 +- app/server/main.js | 1 - 20 files changed, 107 insertions(+), 154 deletions(-) delete mode 100644 app/imports/api/creature/computation/logAlterations.js rename app/imports/api/creature/computation/{creatureComputation.js => recomputeCreature.js} (87%) create mode 100644 app/imports/api/creature/computation/writeAlteredProperties.js delete mode 100644 app/imports/api/creature/computation/writeCreature.js diff --git a/app/imports/api/creature/CreatureProperties.js b/app/imports/api/creature/CreatureProperties.js index 626c5e9d..d3d1701f 100644 --- a/app/imports/api/creature/CreatureProperties.js +++ b/app/imports/api/creature/CreatureProperties.js @@ -1,6 +1,6 @@ import SimpleSchema from 'simpl-schema'; import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema.js'; -import { recomputeCreature } from '/imports/api/creature/computation/creatureComputation.js'; +import { recomputeCreature } from '/imports/api/creature/computation/recomputeCreature.js'; import LibraryNodes from '/imports/api/library/LibraryNodes.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; import { softRemove } from '/imports/api/parenting/softRemove.js'; diff --git a/app/imports/api/creature/computation/ComputationMemo.js b/app/imports/api/creature/computation/ComputationMemo.js index 3f55ee45..d4570766 100644 --- a/app/imports/api/creature/computation/ComputationMemo.js +++ b/app/imports/api/creature/computation/ComputationMemo.js @@ -34,6 +34,7 @@ export default class ComputationMemo { if (this.statsByVariableName[variableName]){ prop.value = NaN; prop.computationDetails.error = 'variableNameCollision'; + console.warn('variableNameCollision', prop); return; } this.statsByVariableName[variableName] = prop; diff --git a/app/imports/api/creature/computation/combineStat.js b/app/imports/api/creature/computation/combineStat.js index e56b7197..8b99c93a 100644 --- a/app/imports/api/creature/computation/combineStat.js +++ b/app/imports/api/creature/computation/combineStat.js @@ -25,7 +25,6 @@ function combineAttribute(stat, aggregator){ function combineSkill(stat, aggregator, memo){ // Skills are based on some ability Modifier - let abilityMod = 0; let ability = memo.statsByVariableName[stat.ability] if (stat.ability && ability){ if (!ability.computationDetails.computed){ @@ -48,9 +47,9 @@ function combineSkill(stat, aggregator, memo){ // Multiply the proficiency bonus by the actual proficiency profBonus *= stat.proficiency; // Combine everything to get the final result - let result = (abilityMod + profBonus + stat.add) * stat.mul; - if (result < stat.min) result = stat.min; - if (result > stat.max) result = stat.max; + let result = (stat.abilityMod + profBonus + aggregator.add) * aggregator.mul; + if (result < aggregator.min) result = aggregator.min; + if (result > aggregator.max) result = aggregator.max; result = Math.floor(result); if (stat.base > result) result = stat.base; stat.value = result; diff --git a/app/imports/api/creature/computation/computeEffect.js b/app/imports/api/creature/computation/computeEffect.js index 455bb65d..53c5a4c6 100644 --- a/app/imports/api/creature/computation/computeEffect.js +++ b/app/imports/api/creature/computation/computeEffect.js @@ -1,10 +1,10 @@ import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js'; export default function computeEffect(effect, memo){ - if (effect.computed) return; + if (effect.computationDetails.computed) return; if (_.isFinite(effect.calculation)){ effect.result = +effect.calculation; - } else if(effect.operation === "conditional" || effect.operation === "rollBonuses"){ + } else if(effect.operation === "conditional" || effect.operation === "rollBonus"){ effect.result = effect.calculation; } else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){ effect.result = 1; diff --git a/app/imports/api/creature/computation/computeStat.js b/app/imports/api/creature/computation/computeStat.js index 0a8f58d6..f2dce516 100644 --- a/app/imports/api/creature/computation/computeStat.js +++ b/app/imports/api/creature/computation/computeStat.js @@ -13,6 +13,7 @@ export default function computeStat(stat, memo){ stat.value = NaN; stat.computationDetails.busyComputing = false; stat.computationDetails.error = 'dependencyLoop'; + console.warn('dependencyLoop', stat); return; } // Compute and aggregate all the effects diff --git a/app/imports/api/creature/computation/computedValueOfVariableName.js b/app/imports/api/creature/computation/computedValueOfVariableName.js index 78e2887b..4349af9f 100644 --- a/app/imports/api/creature/computation/computedValueOfVariableName.js +++ b/app/imports/api/creature/computation/computedValueOfVariableName.js @@ -4,7 +4,7 @@ export default function computedValueOfVariableName(sub, memo){ const stat = memo.statsByVariableName[sub]; if (!stat) return null; if (!stat.computationDetails.computed){ - computeStat(stat, char); + computeStat(stat, memo); } - return stat.result; + return stat.value; } diff --git a/app/imports/api/creature/computation/evaluateCalculation.js b/app/imports/api/creature/computation/evaluateCalculation.js index 110b5fbc..c87e0866 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 * as math from 'mathjs'; export default function evaluateCalculation(string, memo){ if (!string) return string; @@ -7,6 +8,7 @@ export default function evaluateCalculation(string, memo){ try { calc = math.parse(string); } catch (e) { + console.error(e); return string; } // Replace all symbols with known values @@ -20,7 +22,6 @@ export default function evaluateCalculation(string, memo){ return node; } }); - // Evaluate the expression to a number or return with substitutions try { return substitutedCalc.eval(); diff --git a/app/imports/api/creature/computation/logAlterations.js b/app/imports/api/creature/computation/logAlterations.js deleted file mode 100644 index 86d482ef..00000000 --- a/app/imports/api/creature/computation/logAlterations.js +++ /dev/null @@ -1,16 +0,0 @@ -import { isEqual, forOwn } from 'lodash'; -import { ComputedOnlySkilLSchema } from '/imports/api/properties/Skills.js'; - -export default function logAlterations(memo){ - forOwn(memo.originalPropsById, old => { - let changed = memo.propsById[old._id]; - delete changed.computationDetails; - - if (!isEqual(old, changed)){ - console.log({change: {old, changed}}) - } - }); -} - -// TODO use this as a starting point to write only computed fields that have -// changed diff --git a/app/imports/api/creature/computation/creatureComputation.js b/app/imports/api/creature/computation/recomputeCreature.js similarity index 87% rename from app/imports/api/creature/computation/creatureComputation.js rename to app/imports/api/creature/computation/recomputeCreature.js index 33837fb1..9fc3af4a 100644 --- a/app/imports/api/creature/computation/creatureComputation.js +++ b/app/imports/api/creature/computation/recomputeCreature.js @@ -1,14 +1,10 @@ -// TODO allow abilities to get advantage/disadvantage, making all skills that are based -// on them disadvantaged as well - import { ValidatedMethod } from 'meteor/mdg:validated-method'; import SimpleSchema from 'simpl-schema'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import ComputationMemo from '/imports/api/creature/computation/ComputationMemo.js'; import computeMemo from '/imports/api/creature/computation/computeMemo.js'; import getCalculationProperties from '/imports/api/creature/computation/getCalculationProperties.js'; -import logAlterations from '/imports/api/creature/computation/logAlterations.js'; -import * as math from 'mathjs'; +import writeAlteredProperties from '/imports/api/creature/computation/writeAlteredProperties.js'; export const recomputeCreature = new ValidatedMethod({ @@ -67,10 +63,7 @@ export const recomputeCreature = new ValidatedMethod({ export function recomputeCreatureById(creatureId){ let props = getCalculationProperties(creatureId); let computationMemo = new ComputationMemo(props); - console.log({toCompute: computationMemo}); computeMemo(computationMemo); - console.log({computed: computationMemo}); - logAlterations(computationMemo); - //writeAlteredProps(computationMemo); + writeAlteredProperties(computationMemo); return computationMemo; } diff --git a/app/imports/api/creature/computation/writeAlteredProperties.js b/app/imports/api/creature/computation/writeAlteredProperties.js new file mode 100644 index 00000000..04926b9e --- /dev/null +++ b/app/imports/api/creature/computation/writeAlteredProperties.js @@ -0,0 +1,71 @@ +import { isEqual, forOwn } from 'lodash'; +import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js'; +import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js'; +import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js'; +import CreatureProperties from '/imports/api/creature/CreatureProperties.js'; + +export default function writeAlteredProperties(memo){ + let bulkWriteOperations = []; + // Loop through all properties on the memo + forOwn(memo.originalPropsById, (original, _id) => { + let changed = memo.propsById[_id]; + + let schema; + switch (changed.type){ + case 'skill': + schema = ComputedOnlySkillSchema; + break; + case 'attribute': + schema = ComputedOnlyAttributeSchema; + break; + case 'effect': + schema = ComputedOnlyEffectSchema; + break; + } + let op = undefined; + // Loop through all keys that can be changed by computation + // and compile an operation that sets all those keys + for (let key of schema.objectKeys()){ + if (!isEqual(original[key], changed[key])){ + if (!op) op = newOperation(_id, changed.type); + op.updateOne.update.$set[key] = changed[key]; + } + } + if (op){ + bulkWriteOperations.push(op); + } + }); + bulkWriteProperties(bulkWriteOperations); +} + +function newOperation(_id, type){ + let newOp = { + updateOne: { + filter: {_id}, + update: {$set: {}}, + } + }; + if (Meteor.isClient){ + newOp.type = type; + } + return newOp; +}; + +function bulkWriteProperties(bulkWriteOps){ + if (!bulkWriteOps.length) return; + if (Meteor.isServer){ + CreatureProperties.rawCollection().bulkWrite( + bulkWriteOps, + {ordered : false}, + function(e){ + if (e) console.error(e); + } + ); + } else { + _.each(bulkWriteOps, op => { + CreatureProperties.update(op.updateOne.filter, op.updateOne.update, { + selector: {type: op.type} + }); + }); + } +} diff --git a/app/imports/api/creature/computation/writeCreature.js b/app/imports/api/creature/computation/writeCreature.js deleted file mode 100644 index d5c4febe..00000000 --- a/app/imports/api/creature/computation/writeCreature.js +++ /dev/null @@ -1,98 +0,0 @@ - -function writeCreature(char) { - //TODO these functions don't filter the stats before trying to write - writeAttributes(char); - writeSkills(char); - writeDamageMultipliers(char); - writeEffects(char); -} - -/* - * Write all the attributes from the in-memory char object to the Attirbute docs - */ -function writeAttributes(char) { - let bulkWriteOps = _.map(char.atts, (att, variableName) => { - let op = { - updateMany: { - filter: {'ancestors.id': char.id, variableName}, - update: {'$set': { - value: att.result, - rollBonuses: skill.rollBonus, - }}, - } - }; - if (typeof att.mod === 'number'){ - op.updateMany.update.$set.mod = att.mod; - } else { - op.updateMany.update.$unset = {mod: 1}; - } - return op; - }); - bulkWriteProperties({bulkWriteOps, selectorType: 'attribute'}); -} - -function writeSkills(char) { - let bulkWriteOps = _.map(char.skills, (skill, variableName) => { - let op = { - updateMany: { - filter: {'ancestors.id': char.id, variableName}, - update: {$set: { - value: skill.result, - abilityMod: skill.abilityMod, - advantage: skill.advantage, - passiveBonus: skill.passiveAdd, - proficiency: skill.proficiency, - conditionalBenefits: skill.conditional, - rollBonuses: skill.rollBonus, - fail: skill.fail, - }}, - } - }; - return op; - }); - bulkWriteProperties({bulkWriteOps, selectorType: 'skill'}); -} - -function writeDamageMultipliers(char) { - let bulkWriteOps = _.map(char.dms, (dm, variableName) => { - let op = { - updateMany: { - filter: {'ancestors.id': char.id, variableName}, - update: {$set: { - value: dm.result, - }}, - } - }; - return op; - }); - bulkWriteProperties({bulkWriteOps, selectorType: 'damageMultiplier'}); -} - -function writeEffects(char){ - let bulkWriteOps = _.map(char.computedEffects, effect => ({ - updateOne: { - filter: {_id: effect._id}, - update: {$set: { - result: effect.result, - }}, - }, - })); - if (!bulkWriteOps.length) return; - bulkWriteProperties({bulkWriteOps, selectorType: 'effect'}); -} - -function bulkWriteProperties({bulkWriteOps, selectorType}){ - if (!bulkWriteOps.length) return; - if (Meteor.isServer){ - CreatureProperties.rawCollection().bulkWrite(bulkWriteOps, {ordered : false}, function(e){ - if (e) console.error(e); - }); - } else { - _.each(bulkWriteOps, op => { - CreatureProperties.update(op.updateMany.filter, op.updateMany.update, { - multi: true, - selector: {type: selectorType} - }); - }); - } -} diff --git a/app/imports/api/creature/creatureComputation.test.js b/app/imports/api/creature/creatureComputation.test.js index 19c64c88..783d8cae 100644 --- a/app/imports/api/creature/creatureComputation.test.js +++ b/app/imports/api/creature/creatureComputation.test.js @@ -1,4 +1,4 @@ -import {computeCreature} from "./creatureComputation.js"; +import {computeCreature} from "./recomputeCreature.js"; import assert from "assert"; const makeEffect = function(operation, value){ diff --git a/app/imports/api/creature/mixins/recomputeCreatureMixin.js b/app/imports/api/creature/mixins/recomputeCreatureMixin.js index a69cfc2e..cc07e8f0 100644 --- a/app/imports/api/creature/mixins/recomputeCreatureMixin.js +++ b/app/imports/api/creature/mixins/recomputeCreatureMixin.js @@ -1,4 +1,4 @@ -import { recomputeCreatureById } from '/imports/api/creature/computation/creatureComputation.js'; +import { recomputeCreatureById } from '/imports/api/creature/computation/recomputeCreature.js'; export default function recomputeCreatureMixin(methodOptions){ let runFunc = methodOptions.run; diff --git a/app/imports/api/parenting/parenting.js b/app/imports/api/parenting/parenting.js index 3e6506e9..64a80b5b 100644 --- a/app/imports/api/parenting/parenting.js +++ b/app/imports/api/parenting/parenting.js @@ -196,13 +196,6 @@ export function updateParent({docRef, parentRef}){ }); } -// TODO move these functions to character properties collection -export function findEnabled(collection, query, options){ - query.enabled = true; - query['ancestors.$.enabled'] = {$not: false}; - return collection.find(query, options); -} - export function getName(doc){ if (doc.name) return name; var i = doc.ancestors.length; diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index 33b32615..dd58ede9 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -67,7 +67,7 @@ let AttributeSchema = new SimpleSchema({ }, }); -let ComputedAttributeSchema = new SimpleSchema({ +let ComputedOnlyAttributeSchema = new SimpleSchema({ // The computed value of the attribute value: { type: Number, @@ -78,6 +78,10 @@ let ComputedAttributeSchema = new SimpleSchema({ type: SimpleSchema.Integer, optional: true, }, -}).extend(AttributeSchema); +}); -export { AttributeSchema, ComputedAttributeSchema }; +const ComputedAttributeSchema = new SimpleSchema() + .extend(ComputedOnlyAttributeSchema) + .extend(AttributeSchema); + +export { AttributeSchema, ComputedOnlyAttributeSchema, ComputedAttributeSchema }; diff --git a/app/imports/api/properties/DamageMultipliers.js b/app/imports/api/properties/DamageMultipliers.js index 5ea420b7..c1400630 100644 --- a/app/imports/api/properties/DamageMultipliers.js +++ b/app/imports/api/properties/DamageMultipliers.js @@ -1,6 +1,5 @@ import SimpleSchema from 'simpl-schema'; import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js'; -// TODO consider damage types as an array that applies to multiple types at once /* * DamageMultipliers are multipliers that affect how much damage is taken from diff --git a/app/imports/api/properties/Effects.js b/app/imports/api/properties/Effects.js index b88887f8..76db387d 100644 --- a/app/imports/api/properties/Effects.js +++ b/app/imports/api/properties/Effects.js @@ -51,12 +51,16 @@ const StoredEffectSchema = new SimpleSchema({ }, }).extend(EffectSchema); -const ComputedEffectSchema = new SimpleSchema({ +const ComputedOnlyEffectSchema = new SimpleSchema({ // The computed result of the effect result: { type: SimpleSchema.oneOf(Number, String), optional: true, }, -}).extend(EffectSchema); +}) -export { EffectSchema, StoredEffectSchema, ComputedEffectSchema }; +const ComputedEffectSchema = new SimpleSchema() + .extend(ComputedOnlyEffectSchema) + .extend(EffectSchema); + +export { EffectSchema, StoredEffectSchema, ComputedEffectSchema, ComputedOnlyEffectSchema }; diff --git a/app/imports/api/properties/Skills.js b/app/imports/api/properties/Skills.js index c68f1d67..3597d097 100644 --- a/app/imports/api/properties/Skills.js +++ b/app/imports/api/properties/Skills.js @@ -79,7 +79,7 @@ let ComputedOnlySkillSchema = new SimpleSchema({ allowedValues: [0, 0.5, 1, 2], defaultValue: 0, }, - // Computed number of total conditional benefits + // Compiled text of all conditional benefits conditionalBenefits: { type: Array, optional: true, @@ -87,7 +87,7 @@ let ComputedOnlySkillSchema = new SimpleSchema({ 'conditionalBenefits.$': { type: String, }, - // Computed number of things forcing this skill to fail + // Compiled text of all roll bonuses rollBonuses: { type: Array, optional: true, @@ -102,6 +102,8 @@ let ComputedOnlySkillSchema = new SimpleSchema({ }, }) -let ComputedSkillSchema = ComputedOnlySkillSchema.extend(SkillSchema); +const ComputedSkillSchema = new SimpleSchema() + .extend(ComputedOnlySkillSchema) + .extend(SkillSchema); export { SkillSchema, ComputedSkillSchema, ComputedOnlySkillSchema }; diff --git a/app/imports/ui/creature/character/CharacterSheet.vue b/app/imports/ui/creature/character/CharacterSheet.vue index 8dcf5017..e1244c8a 100644 --- a/app/imports/ui/creature/character/CharacterSheet.vue +++ b/app/imports/ui/creature/character/CharacterSheet.vue @@ -102,7 +102,7 @@ import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue'; import PersonaTab from '/imports/ui/creature/character/characterSheetTabs/PersonaTab.vue'; import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue'; - import { recomputeCreature } from '/imports/api/creature/computation/creatureComputation.js'; + import { recomputeCreature } from '/imports/api/creature/computation/recomputeCreature.js'; export default { props: { diff --git a/app/server/main.js b/app/server/main.js index 538329ec..b4cfce7b 100644 --- a/app/server/main.js +++ b/app/server/main.js @@ -1,5 +1,4 @@ import "/imports/server/publications/index.js"; -import "/imports/api/creature/computation/creatureComputation.js"; import "/imports/api/parenting/deleteRemovedDocuments.js"; import "/imports/server/config/simpleSchemaDebug.js"; import "/imports/api/parenting/organizeMethods.js";