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 getActiveProperties from '/imports/api/creature/getActiveProperties.js'; import writeAlteredProperties from '/imports/api/creature/computation/writeAlteredProperties.js'; import writeCreatureVariables from '/imports/api/creature/computation/writeCreatureVariables.js'; import { recomputeDamageMultipliersById } from '/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js' export const recomputeCreature = new ValidatedMethod({ name: 'Creatures.methods.recomputeCreature', validate: new SimpleSchema({ charId: { type: String } }).validator(), run({charId}) { // Permission assertEditPermission(charId, this.userId); // Work, call this direcly if you are already in a method that has checked // for permission to edit a given character recomputeCreatureById(charId); }, }); const calculationPropertyTypes = [ 'attribute', 'skill', 'effect', 'proficiency', 'classLevel', 'toggle', ]; /** * This function is the heart of DiceCloud. It recomputes a creature's stats, * distilling down effects and proficiencies into the final stats that make up * a creature. * * Essentially this is a depth first tree traversal algorithm that computes * stats' dependencies before computing stats themselves, while detecting * dependency loops. * * At the moment it makes no effort to limit recomputation to just what was * changed. * * Attempting to implement dependency management to limit recomputation to just * change affected stats should only happen as a last resort, when this function * can no longer be performed more efficiently, and server resources can not be * expanded to meet demand. * * A brief overview: * - Fetch the stats of the creature and add them to * an object for quick lookup * - Fetch the effects and proficiencies which apply to each stat and store them with the stat * - Fetch the class levels and store them as well * - Mark each stat and effect as uncomputed * - Iterate over each stat in order and compute it * - If the stat is already computed, skip it * - If the stat is busy being computed, we are in a dependency loop, make it NaN and mark computed * - Mark the stat as busy computing * - Iterate over each effect which applies to the attribute * - If the effect is not computed compute it * - If the effect relies on another attribute, get its computed value * - Recurse if that attribute is uncomputed * - apply the effect to the attribute * - Conglomerate all the effects to compute the final stat values * - Mark the stat as computed * - Write the computed results back to the database */ export function recomputeCreatureById(creatureId){ let props = getActiveProperties({ ancestorId: creatureId, filter: {type: {$in: calculationPropertyTypes}}, includeUntoggled: true, }); let computationMemo = new ComputationMemo(props); computeMemo(computationMemo); writeAlteredProperties(computationMemo); writeCreatureVariables(computationMemo, creatureId); // if(Meteor.isClient) console.log(computationMemo); recomputeDamageMultipliersById(creatureId); return computationMemo; }