import combineStat from '/imports/api/creature/computation/engine/combineStat.js'; import computeEffect from '/imports/api/creature/computation/engine/computeEffect.js'; import EffectAggregator from '/imports/api/creature/computation/engine/EffectAggregator.js'; import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js'; import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js'; import { each, union, without } from 'lodash'; export default function computeStat(stat, memo){ // If the stat is already computed, skip it if (stat.computationDetails.computed) return; if (stat.computationDetails.busyComputing){ // Trying to compute this stat again while it is already computing. // We must be in a dependency loop. stat.computationDetails.computed = true; stat.value = NaN; stat.computationDetails.busyComputing = false; stat.computationDetails.error = 'dependencyLoop'; if (Meteor.isClient) console.warn('dependencyLoop', stat); return; } // Before doing any work, mark this stat as busy stat.computationDetails.busyComputing = true; let effects = stat.computationDetails.effects; let proficiencies = stat.computationDetails.proficiencies; // Get references to all the stats that share the variable name let sameNameStats = stat.computationDetails.idsOfSameName.map( id => memo.propsById[id] ); let allStats = [stat, ...sameNameStats]; // Decide which stat is the last active stat // The last active stat is considered the cannonical stat let lastActiveStat; allStats.forEach(candidateStat => { applyToggles(candidateStat, memo); if (!candidateStat.inactive) lastActiveStat = candidateStat; candidateStat.overridden = undefined; }); if (!lastActiveStat){ delete memo.statsByVariableName[stat.variableName]; return; } // Make sure the active stat has all the effects and proficiencies lastActiveStat.computationDetails.effects = effects; lastActiveStat.computationDetails.proficiencies = proficiencies; // Update the memo's stat with the chosen stat memo.statsByVariableName[stat.variableName] = lastActiveStat; // Recreate list of the non-cannonical stats sameNameStats = without(allStats, lastActiveStat); sameNameStats.forEach(statInstance => { // Mark the non-cannonical stats as overridden statInstance.overridden = true; // Apply the cannonical damage statInstance.damage = lastActiveStat.damage; }); let baseDependencies = []; allStats.forEach(statInstance => { // Add this stat and its deps to the dependencies baseDependencies = union( baseDependencies, [statInstance._id], statInstance.dependencies, ); // Apply all the base proficiencies if (statInstance.baseProficiency && !statInstance.inactive){ proficiencies.push({ value: statInstance.baseProficiency, stats: [statInstance.variableName], type: 'proficiency', dependencies: [], computationDetails: { computed: true, } }); } // Compute each active stat's baseValue calculation and apply it if (statInstance.baseValueCalculation) { let { result, context, dependencies } = evaluateCalculation({ string: statInstance.baseValueCalculation, prop: statInstance, memo }); baseDependencies = union(baseDependencies, dependencies); statInstance.baseValue = +result.value; if (context.errors.length){ statInstance.baseValueErrors = context.errors; } // Apply all the base values if (!statInstance.inactive){ effects.push({ operation: 'base', calculation: statInstance.baseValueCalculation, result: statInstance.baseValue, stats: [statInstance.variableName], dependencies: [], computationDetails: { computed: true, }, }); } } }); // Apply all the base baseDependencies allStats.forEach(statInstance => { statInstance.dependencies = union( statInstance.dependencies, without(baseDependencies, statInstance._id) ); }); // Compute and aggregate all the effects let aggregator = new EffectAggregator(); let effectDeps = []; each(effects, (effect) => { // Compute computeEffect(effect, memo); if (effect.deactivatedByToggle) return; // dependencies if (effect._id) effectDeps = [effect._id]; effectDeps = union(effectDeps, effect.dependencies); // Add computed effect to aggregator aggregator.addEffect(effect); }); // Combine the effects into the stats allStats.forEach(statInstance => { // Conglomerate all the effects to compute the final stat values combineStat(statInstance, aggregator, memo); // Mark the stats as computed statInstance.computationDetails.computed = true; statInstance.computationDetails.busyComputing = false; statInstance.dependencies = union( statInstance.dependencies, effectDeps ); }); }