Got tests running on single property character
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import computeStat from '/imports/api/creature/computation/engine/computeStat.js';
|
import computeStat from '/imports/api/creature/computation/engine/computeStat.js';
|
||||||
import computeProficiency from '/imports/api/creature/computation/engine/computeProficiency.js';
|
import computeProficiency from '/imports/api/creature/computation/engine/computeProficiency.js';
|
||||||
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
||||||
import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js';
|
import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js';
|
||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
|
|
||||||
export default function combineStat(stat, aggregator, memo){
|
export default function combineStat(stat, aggregator, memo){
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import createGraph from 'ngraph.graph';
|
||||||
|
|
||||||
|
export default class CreatureComputation {
|
||||||
|
constructor(properties){
|
||||||
|
// Set up fields
|
||||||
|
this.originalPropsById = {};
|
||||||
|
this.propsById = {};
|
||||||
|
this.propsByType = {};
|
||||||
|
this.propsByVariableName = {};
|
||||||
|
this.props = properties;
|
||||||
|
this.dependencyGraph = createGraph();
|
||||||
|
|
||||||
|
// Store properties for easy access later
|
||||||
|
properties.forEach(prop => {
|
||||||
|
// Store a copy of the unmodified prop
|
||||||
|
this.originalPropsById[prop._id] = cloneDeep(prop);
|
||||||
|
|
||||||
|
// Store by id
|
||||||
|
this.propsById[prop._id] = prop;
|
||||||
|
|
||||||
|
// Store by type
|
||||||
|
this.propsByType[prop.type] ?
|
||||||
|
this.propsByType[prop.type].push(prop) :
|
||||||
|
this.propsByType[prop.type] = [prop];
|
||||||
|
|
||||||
|
// Store by variableName
|
||||||
|
this.propsByVariableName[prop.variableName] ?
|
||||||
|
this.propsByVariableName[prop.variableName].push(prop) :
|
||||||
|
this.propsByVariableName[prop.variableName]= [prop];
|
||||||
|
|
||||||
|
// Store the prop in the dependency graph
|
||||||
|
this.dependencyGraph.addNode(prop._id, prop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ export default function linkCalculationDependencies(dependencyGraph, prop, {prop
|
|||||||
// ancestors: {} //this gets added if there are resolved ancestors
|
// ancestors: {} //this gets added if there are resolved ancestors
|
||||||
};
|
};
|
||||||
// Traverse the parsed calculation looking for variable names
|
// Traverse the parsed calculation looking for variable names
|
||||||
calcObj._parsedCalculation.travese(node => {
|
calcObj._parsedCalculation.traverse(node => {
|
||||||
// Skip nodes that aren't symbols or accessors
|
// Skip nodes that aren't symbols or accessors
|
||||||
if (!(node instanceof SymbolNode || node instanceof AccessorNode)) return;
|
if (!(node instanceof SymbolNode || node instanceof AccessorNode)) return;
|
||||||
// Link ancestor references as direct property dependencies
|
// Link ancestor references as direct property dependencies
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export default function linkInventory(forest, dependencyGraph){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleProp(prop, containerStack, dependencyGraph){
|
function handleProp(prop, containerStack, dependencyGraph){
|
||||||
|
// Skip props that aren't part of the inventory
|
||||||
|
if (prop.type !== 'inventory' && prop.type !== 'container') return;
|
||||||
// Determine if this property is carried, items are carried by default
|
// Determine if this property is carried, items are carried by default
|
||||||
let carried = prop.type === 'container' ? prop.carried : true;
|
let carried = prop.type === 'container' ? prop.carried : true;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const linkDependenciesByType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function linkTypeDependencies(dependencyGraph, prop){
|
export default function linkTypeDependencies(dependencyGraph, prop){
|
||||||
linkDependenciesByType[prop.type]?.(prop);
|
linkDependenciesByType[prop.type]?.(dependencyGraph, prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkClassLevel(dependencyGraph, prop){
|
function linkClassLevel(dependencyGraph, prop){
|
||||||
|
|||||||
@@ -1,20 +1,54 @@
|
|||||||
|
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||||
import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js';
|
import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
export default function parseCalculationFields(prop, schemas){
|
export default function parseCalculationFields(prop, schemas){
|
||||||
|
parseInlineCalculationFields(prop, schemas);
|
||||||
|
parseDirectCalculationFields(prop, schemas);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseInlineCalculationFields(prop, schemas){
|
||||||
|
// For each key in the schema
|
||||||
|
schemas[prop.type]._schemaKeys.forEach( key => {
|
||||||
|
// That ends in .inlineCalculations
|
||||||
|
if (key.slice(-19) === '.inlineCalculations'){
|
||||||
|
const inlineCalcKey = key.slice(0, -19);
|
||||||
|
applyFnToKey(prop, inlineCalcKey, (prop, key) => {
|
||||||
|
const inlineCalcObj = get(prop, key);
|
||||||
|
if (!inlineCalcObj) return;
|
||||||
|
// Store a reference to all the inline calculations
|
||||||
|
prop._computationDetails.inlineCalculations.push(inlineCalcObj);
|
||||||
|
// Extract the calculations and store them on the property
|
||||||
|
let string = inlineCalcObj.text;
|
||||||
|
inlineCalcObj.inlineCalculations = [];
|
||||||
|
let matches = string.matchAll(INLINE_CALCULATION_REGEX);
|
||||||
|
for (let match of matches){
|
||||||
|
let calculation = match[1];
|
||||||
|
inlineCalcObj.inlineCalculations.push({
|
||||||
|
calculation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDirectCalculationFields(prop, schemas){
|
||||||
// For each key in the schema
|
// For each key in the schema
|
||||||
schemas[prop.type]._schemaKeys.forEach( key => {
|
schemas[prop.type]._schemaKeys.forEach( key => {
|
||||||
// that ends in '.calculation'
|
// that ends in '.calculation'
|
||||||
if (key.slice(-12) === '.calculation'){
|
if (key.slice(-12) === '.calculation'){
|
||||||
const calcKey = key.sclice(0, -12);
|
const calcKey = key.slice(0, -12);
|
||||||
|
|
||||||
// Determine the level the calculation should compute down to
|
// Determine the level the calculation should compute down to
|
||||||
let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel;
|
let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel || 'reduce';
|
||||||
|
|
||||||
// For all fields matching they keys
|
// For all fields matching they keys
|
||||||
// supports `keys.$.with.$.arrays`
|
// supports `keys.$.with.$.arrays`
|
||||||
applyFnToKey(prop, calcKey, calcObj => {
|
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||||
|
const calcObj = get(prop, key);
|
||||||
|
if (!calcObj) return;
|
||||||
// Store a reference to all the calculations
|
// Store a reference to all the calculations
|
||||||
prop._computationDetails.calculations.push(calcObj);
|
prop._computationDetails.calculations.push(calcObj);
|
||||||
// Store the level to compute down to later
|
// Store the level to compute down to later
|
||||||
@@ -23,14 +57,7 @@ export default function parseCalculationFields(prop, schemas){
|
|||||||
parseCalculation(calcObj);
|
parseCalculation(calcObj);
|
||||||
});
|
});
|
||||||
// Or that ends in .inlineCalculations
|
// Or that ends in .inlineCalculations
|
||||||
} else if (key.slice(-19) === '.inlineCalculations'){
|
|
||||||
const inlineCalcKey = key.sclice(0, -19);
|
|
||||||
applyFnToKey(prop, inlineCalcKey, inlineCalcObj => {
|
|
||||||
// Store a reference to all the inline calculations
|
|
||||||
prop._computationDetails.inlineCalculations.push(inlineCalcObj);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import applyFnToKey from '../utility/applyFnToKey.js';
|
||||||
|
import { unset } from 'lodash';
|
||||||
|
|
||||||
|
export default function removeSchemaFields(schemas, prop){
|
||||||
|
schemas.forEach(schema => {
|
||||||
|
schema._schemaKeys.forEach(key => {
|
||||||
|
// Skip object keys
|
||||||
|
if (schema.getQuickTypeForKey(key) === 'object') return;
|
||||||
|
// Unset other computed only keys
|
||||||
|
applyFnToKey(prop, key, unset)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,17 +4,16 @@ import CreatureProperties,
|
|||||||
from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
||||||
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||||
import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js';
|
import linkInventory from './buildComputation/linkInventory.js';
|
||||||
import { cloneDeep, unset } from 'lodash';
|
import walkDown from './utility/walkdown.js';
|
||||||
import createGraph from 'ngraph.graph';
|
import parseCalculationFields from './buildComputation/parseCalculationFields.js';
|
||||||
import linkInventory from '/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js';
|
import computeInactiveStatus from './buildComputation/computeInactiveStatus.js';
|
||||||
import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdown.js';
|
import computeToggleDependencies from './buildComputation/computeToggleDependencies.js';
|
||||||
import parseCalculationFields from '/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js';
|
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
||||||
import computeInactiveStatus from '/imports/api/creature/computation/newEngine/buildComputation/computeInactiveStatus.js';
|
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
||||||
import computeToggleDependencies from '/imports/api/creature/computation/newEngine/buildComputation/computeToggleDependencies.js';
|
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
||||||
import linkCalculationDependencies from '/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js';
|
import CreatureComputation from './buildComputation/CreatureComputation.js';
|
||||||
import linkTypeDependencies from '/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js';
|
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
||||||
import computeSlotQuantityFilled from '/imports/api/creature/computation/newEngine/buildComputation/computeSlotQuantityFilled.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store index of properties
|
* Store index of properties
|
||||||
@@ -50,50 +49,32 @@ function getProperties(creatureId){
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildComputationFromProps(properties){
|
export function buildComputationFromProps(properties){
|
||||||
|
|
||||||
|
const computation = new CreatureComputation(properties);
|
||||||
// Dependency graph where edge(a, b) means a depends on b
|
// Dependency graph where edge(a, b) means a depends on b
|
||||||
// The graph includes all dependencies even of inactive properties
|
// The graph includes all dependencies even of inactive properties
|
||||||
// such that any properties changing without changing their dependencies
|
// such that any properties changing without changing their dependencies
|
||||||
// can limit the recompute to connected parts of the graph
|
// can limit the recompute to connected parts of the graph
|
||||||
// Each node's data represents a prop or a virtual prop like a variable
|
// Each node's data represents a prop or a virtual prop like a variable
|
||||||
// Each link's data is a string representing the link type
|
// Each link's data is a string representing the link type
|
||||||
const dependencyGraph = createGraph();
|
const dependencyGraph = computation.dependencyGraph;
|
||||||
|
|
||||||
const computation = {
|
|
||||||
originalPropsById: {},
|
|
||||||
propsById: {},
|
|
||||||
propsByType: {},
|
|
||||||
propsByVariableName: {},
|
|
||||||
props: properties,
|
|
||||||
dependencyGraph,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Process the properties one by one
|
// Process the properties one by one
|
||||||
properties.forEach(prop => {
|
properties.forEach(prop => {
|
||||||
|
|
||||||
// Store the prop in the memo by type, variableName and id
|
let computedSchema = computedOnlySchemas[prop.type];
|
||||||
storePropInMemo(prop, computation)
|
removeSchemaFields([computedSchema, denormSchema], prop);
|
||||||
|
|
||||||
// Store the prop in the dependency graph
|
|
||||||
dependencyGraph.addNode(prop._id, prop);
|
|
||||||
|
|
||||||
// Remove old computed only fields
|
|
||||||
computedOnlySchemas[prop.type]._schemaKeys.forEach(key =>
|
|
||||||
applyFnToKey(prop, key, unset)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove old denormalised fields
|
|
||||||
denormSchema._schemaKeys.forEach(key =>
|
|
||||||
applyFnToKey(prop, key, unset)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a place to store all the computation details
|
// Add a place to store all the computation details
|
||||||
prop._computationDetails = {
|
prop._computationDetails = {
|
||||||
calculations: [],
|
calculations: [],
|
||||||
|
inlineCalculations: [],
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse all the calculations
|
// Parse all the calculations
|
||||||
parseCalculationFields(prop, computedSchemas);
|
parseCalculationFields(prop, computedSchemas);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all the properties as trees based on their ancestors
|
// Get all the properties as trees based on their ancestors
|
||||||
@@ -108,7 +89,7 @@ export function buildComputationFromProps(properties){
|
|||||||
// Link the inventory dependencies
|
// Link the inventory dependencies
|
||||||
linkInventory(forest, dependencyGraph);
|
linkInventory(forest, dependencyGraph);
|
||||||
|
|
||||||
// Graph functions that rely on the props being stored first
|
// Link functions that require the above to be complete
|
||||||
properties.forEach(prop => {
|
properties.forEach(prop => {
|
||||||
linkTypeDependencies(dependencyGraph, prop, computation);
|
linkTypeDependencies(dependencyGraph, prop, computation);
|
||||||
linkCalculationDependencies(dependencyGraph, prop, computation);
|
linkCalculationDependencies(dependencyGraph, prop, computation);
|
||||||
@@ -116,19 +97,3 @@ export function buildComputationFromProps(properties){
|
|||||||
|
|
||||||
return computation;
|
return computation;
|
||||||
}
|
}
|
||||||
|
|
||||||
function storePropInMemo(prop, memo){
|
|
||||||
// Store dicts for easy access later
|
|
||||||
// Store a copy of the unmodified prop
|
|
||||||
memo.originalPropsById[prop._id] = cloneDeep(prop);
|
|
||||||
// Store by id
|
|
||||||
memo.propsById[prop._id] = prop;
|
|
||||||
// Store by type
|
|
||||||
memo.propsByType[prop.type] ?
|
|
||||||
memo.propsByType[prop.type].push(prop) :
|
|
||||||
memo.propsByType[prop.type] = [prop];
|
|
||||||
// Store by variableName
|
|
||||||
memo.propsByVariableName[prop.variableName] ?
|
|
||||||
memo.propsByVariableName[prop.variableName].push(prop) :
|
|
||||||
memo.propsByVariableName[prop.variableName]= [prop];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
|
||||||
describe('buildComputation', function(){
|
describe('buildComputation', function(){
|
||||||
it('Builds something', function(){
|
it('Builds something at all', function(){
|
||||||
let computation = buildComputationFromProps(testProperties);
|
let computation = buildComputationFromProps(testProperties);
|
||||||
console.log(computation);
|
assert.exists(computation);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -13,6 +13,14 @@ var testProperties = [
|
|||||||
clean({
|
clean({
|
||||||
_id: 'attributeId123',
|
_id: 'attributeId123',
|
||||||
type: 'attribute',
|
type: 'attribute',
|
||||||
|
variableName: 'strength',
|
||||||
|
attributeType: 'ability',
|
||||||
|
baseValue: {
|
||||||
|
calculation: '1 + 2 + 3',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
text: 'strength is {strength}'
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import aggregate from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/aggregate/index.js';
|
import aggregate from './computeVariable/aggregate/index.js';
|
||||||
import computeVariableAsAttribute from '/imports/api/creature/computation/newEngine/computeComputation/computeVariableAsType/computeVariableAsAttribute.js';
|
import computeVariableAsAttribute from './computeVariable/computeVariableAsAttribute.js';
|
||||||
import computeVariableAsSkill from '/imports/api/creature/computation/newEngine/computeComputation/computeVariableAsType/computeVariableAsSkill.js';
|
import computeVariableAsSkill from './computeVariable/computeVariableAsSkill.js';
|
||||||
import computeVariableAsConstant from '/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsConstant.js';
|
import computeVariableAsConstant from './computeVariable/computeVariableAsConstant.js';
|
||||||
import computeVariableAsClass from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/computeVariableAsClass.js';
|
import computeVariableAsClass from './computeVariable/computeVariableAsClass.js';
|
||||||
import computeImplicitVariable from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/computeImplicitVariable.js';
|
import computeImplicitVariable from './computeVariable/computeImplicitVariable.js';
|
||||||
|
|
||||||
export default function computeVariable(graph, node, scope){
|
export default function computeVariable(graph, node, scope){
|
||||||
if (!node.data) node.data = {};
|
if (!node.data) node.data = {};
|
||||||
@@ -16,6 +16,7 @@ export default function computeVariable(graph, node, scope){
|
|||||||
// Otherwise add an implicit variable to the scope
|
// Otherwise add an implicit variable to the scope
|
||||||
scope[node.id] = computeImplicitVariable(node, scope);
|
scope[node.id] = computeImplicitVariable(node, scope);
|
||||||
}
|
}
|
||||||
|
console.log('computed variable ', node);
|
||||||
}
|
}
|
||||||
|
|
||||||
function aggregateLinks(graph, node){
|
function aggregateLinks(graph, node){
|
||||||
@@ -40,13 +41,14 @@ function aggregateLinks(graph, node){
|
|||||||
|
|
||||||
function combineAggregations(node, scope){
|
function combineAggregations(node, scope){
|
||||||
combineMultiplierAggregator(node);
|
combineMultiplierAggregator(node);
|
||||||
node.overridenProps.forEach(prop => {
|
node.data.overridenProps?.forEach(prop => {
|
||||||
computeVariableProp(node, prop, scope);
|
computeVariableProp(node, prop, scope);
|
||||||
});
|
});
|
||||||
computeVariableProp(node, node.definingProp, scope);
|
computeVariableProp(node, node.data.definingProp, scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeVariableProp(node, prop, scope){
|
function computeVariableProp(node, prop, scope){
|
||||||
|
if (!prop) return;
|
||||||
if (prop.type === 'attribute'){
|
if (prop.type === 'attribute'){
|
||||||
computeVariableAsAttribute(node, prop, scope)
|
computeVariableAsAttribute(node, prop, scope)
|
||||||
} else if (prop.type === 'skill'){
|
} else if (prop.type === 'skill'){
|
||||||
@@ -61,6 +63,7 @@ function computeVariableProp(node, prop, scope){
|
|||||||
function combineMultiplierAggregator(node){
|
function combineMultiplierAggregator(node){
|
||||||
// get a reference to the aggregator
|
// get a reference to the aggregator
|
||||||
const aggregator = node.data.multiplierAggregator;
|
const aggregator = node.data.multiplierAggregator;
|
||||||
|
if (!aggregator) return;
|
||||||
|
|
||||||
// Combine
|
// Combine
|
||||||
let value;
|
let value;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import getAggregatorResult from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/getAggregatorResult.js';
|
import getAggregatorResult from './getAggregatorResult.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Variables with effects, proficiencies, or damage multipliers but no defining
|
* Variables with effects, proficiencies, or damage multipliers but no defining
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import getAggregatorResult from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/getAggregatorResult.js';
|
import getAggregatorResult from './getAggregatorResult.js';
|
||||||
|
|
||||||
export default function computeVariableAsAttribute(node, prop, scope){
|
export default function computeVariableAsAttribute(node, prop, scope){
|
||||||
let result = getAggregatorResult(node);
|
let result = getAggregatorResult(node);
|
||||||
|
console.log('computing variable as attribure ', node);
|
||||||
prop.total = result;
|
prop.total = result;
|
||||||
prop.value = prop.total - (prop.damage || 0);
|
prop.value = prop.total - (prop.damage || 0);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { CompilationContext } from '/imports/parser/parser.js';
|
|||||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||||
|
|
||||||
export default function computeCalculations(node, scope){
|
export default function computeCalculations(node, scope){
|
||||||
|
if (!node.data) return;
|
||||||
// evaluate all the calculations
|
// evaluate all the calculations
|
||||||
node.data._computationDetails?.calculations?.forEach(calcObj => {
|
node.data._computationDetails?.calculations?.forEach(calcObj => {
|
||||||
evaluateCalculation(calcObj, scope)
|
evaluateCalculation(calcObj, scope)
|
||||||
@@ -14,7 +15,7 @@ export default function computeCalculations(node, scope){
|
|||||||
function evaluateCalculation(calculation, scope){
|
function evaluateCalculation(calculation, scope){
|
||||||
const context = new CompilationContext();
|
const context = new CompilationContext();
|
||||||
const parseNode = calculation._parsedCalculation;
|
const parseNode = calculation._parsedCalculation;
|
||||||
const fn = calculation._parseLevel || 'reduce';
|
const fn = calculation._parseLevel;
|
||||||
const calculationScope = {...calculation._localScope, ...scope};
|
const calculationScope = {...calculation._localScope, ...scope};
|
||||||
calculation.value = parseNode[fn](calculationScope, context);
|
calculation.value = parseNode[fn](calculationScope, context);
|
||||||
calculation.errors = context.errors;
|
calculation.errors = context.errors;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export default function evaluateToggles(node){
|
export default function evaluateToggles(node){
|
||||||
let prop = node.data;
|
let prop = node.data;
|
||||||
|
if (!prop) return;
|
||||||
let toggles = prop._computationDetails?.toggleAncestors;
|
let toggles = prop._computationDetails?.toggleAncestors;
|
||||||
if (!toggles) return;
|
if (!toggles) return;
|
||||||
toggles.forEach(toggle => {
|
toggles.forEach(toggle => {
|
||||||
|
|||||||
@@ -8,27 +8,23 @@ export default function computeCreatureComputation(computation){
|
|||||||
const scope = {};
|
const scope = {};
|
||||||
const graph = computation.dependencyGraph;
|
const graph = computation.dependencyGraph;
|
||||||
// Add all nodes to the stack
|
// Add all nodes to the stack
|
||||||
graph.forEachNode(node => stack.push({
|
graph.forEachNode(node => stack.push(node));
|
||||||
node,
|
|
||||||
visited: false,
|
|
||||||
visitedChildren: false,
|
|
||||||
}));
|
|
||||||
// Depth first traversal of nodes
|
// Depth first traversal of nodes
|
||||||
while (stack.length){
|
while (stack.length){
|
||||||
let top = stack[stack.length - 1];
|
let top = stack[stack.length - 1];
|
||||||
if (top.visited){
|
if (top._visited){
|
||||||
// The object has already been computed, skip
|
// The object has already been computed, skip
|
||||||
stack.pop();
|
stack.pop();
|
||||||
} else if (top.visitedChildren){
|
} else if (top._visitedChildren){
|
||||||
// Compute the top object of the stack
|
|
||||||
compute(graph, top.node, scope);
|
|
||||||
// Mark the object as visited and remove from stack
|
// Mark the object as visited and remove from stack
|
||||||
top.visited = true;
|
top._visited = true;
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
// Compute the top object of the stack
|
||||||
|
compute(graph, top, scope);
|
||||||
} else {
|
} else {
|
||||||
|
top._visitedChildren = true;
|
||||||
// Push dependencies to graph to be computed first
|
// Push dependencies to graph to be computed first
|
||||||
pushDependenciesToStack(top.node.id, graph, stack);
|
pushDependenciesToStack(top.id, graph, stack);
|
||||||
top.visitedChildren = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,15 +38,5 @@ function compute(graph, node, scope){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pushDependenciesToStack(nodeId, graph, stack){
|
function pushDependenciesToStack(nodeId, graph, stack){
|
||||||
graph.forEachLinkedNode(
|
graph.forEachLinkedNode(nodeId, linkedNode => stack.push(linkedNode), true);
|
||||||
nodeId,
|
|
||||||
(linkedNode) => {
|
|
||||||
stack.push({
|
|
||||||
node: linkedNode,
|
|
||||||
visited: false,
|
|
||||||
visitedChildren: false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
true // enumerate only outbound links
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import computeCreatureComputation from './computeCreatureComputation.js';
|
||||||
|
import { buildComputationFromProps } from './buildCreatureComputation.js';
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
|
||||||
|
describe('Compute compuation', function(){
|
||||||
|
it('Computes something at all', function(){
|
||||||
|
console.time('compute');
|
||||||
|
let computation = buildComputationFromProps(testProperties);
|
||||||
|
computeCreatureComputation(computation);
|
||||||
|
console.timeEnd('compute');
|
||||||
|
assert.exists(computation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var testProperties = [
|
||||||
|
clean({
|
||||||
|
_id: 'attributeId123',
|
||||||
|
type: 'attribute',
|
||||||
|
variableName: 'strength',
|
||||||
|
attributeType: 'ability',
|
||||||
|
baseValue: {
|
||||||
|
calculation: '1 + 2 + 3',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
text: 'strength is {strength}'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
function clean(prop){
|
||||||
|
let schema = CreatureProperties.simpleSchema(prop);
|
||||||
|
return schema.clean(prop);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
export function applyFnToKey(doc, key, fn){
|
export default function applyFnToKey(doc, key, fn){
|
||||||
if (key.includes('$.')){
|
if (key.includes('.$')){
|
||||||
applyToArrayKey(doc, key, fn);
|
applyToArrayKey(doc, key, fn);
|
||||||
} else {
|
} else {
|
||||||
applyToSingleKey(doc, key, fn);
|
applyToSingleKey(doc, key, fn);
|
||||||
@@ -16,26 +16,31 @@ function applyToSingleKey(doc, key, fn){
|
|||||||
/**
|
/**
|
||||||
* Applies the given function to all instances in a document key
|
* Applies the given function to all instances in a document key
|
||||||
* key.$.with.$.subdocs will apply to all key[i...n].with[j...m].subdocs
|
* key.$.with.$.subdocs will apply to all key[i...n].with[j...m].subdocs
|
||||||
|
* Warning: Order might be confusing, it will traverse the deepest array in order
|
||||||
|
* but the shallower arrays in reverse order
|
||||||
*/
|
*/
|
||||||
function applyToArrayKey(doc, key, fn){
|
function applyToArrayKey(doc, key, fn){
|
||||||
const keySplit = key.split('.$');
|
const keySplit = key.split('.$');
|
||||||
|
|
||||||
// Stack based depth first traversal of arrays
|
// Stack based depth first traversal of arrays
|
||||||
|
const array = get(doc, keySplit[0]);
|
||||||
|
if (!array) return;
|
||||||
const stack = [{
|
const stack = [{
|
||||||
array: get(doc, keySplit[0]),
|
array,
|
||||||
paths: keySplit.slice(1),
|
paths: keySplit.slice(1),
|
||||||
currentPath: keySplit[0],
|
currentPath: keySplit[0],
|
||||||
indices: [],
|
indices: [],
|
||||||
}];
|
}];
|
||||||
while(stack.length){
|
while(stack.length){
|
||||||
const state = stack.pop();
|
const state = stack.pop();
|
||||||
for (let index in state.array.length){
|
for (let index in state.array){
|
||||||
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
||||||
if (state.paths.length == 1){
|
if (state.paths.length == 1){
|
||||||
applyToSingleKey(doc, currentPath, fn);
|
applyToSingleKey(doc, currentPath, fn);
|
||||||
} else {
|
} else {
|
||||||
|
const array = get(doc, currentPath);
|
||||||
|
if (!array) return;
|
||||||
stack.push({
|
stack.push({
|
||||||
array: get(doc, currentPath),
|
array,
|
||||||
paths: state.paths.slice(1),
|
paths: state.paths.slice(1),
|
||||||
currentPath,
|
currentPath,
|
||||||
indices: [...state.indices, index],
|
indices: [...state.indices, index],
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import applyFnToKey from './applyFnToKey.js';
|
||||||
|
import { assert } from 'chai';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
describe('apply function to key', function(){
|
||||||
|
it('uses a basic key correctly', function(){
|
||||||
|
let obj = getStartingObject();
|
||||||
|
applyFnToKey(obj, 'fox.name', (doc, key) => {
|
||||||
|
assert.equal(obj, doc);
|
||||||
|
assert.equal(key, 'fox.name');
|
||||||
|
assert.equal(get(doc, key), 'foxy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('uses a single nested key correctly', function(){
|
||||||
|
let obj = getStartingObject();
|
||||||
|
let foxSounds = [];
|
||||||
|
applyFnToKey(obj, 'fox.sound.$', (doc, key) => {
|
||||||
|
foxSounds.push(get(doc, key));
|
||||||
|
});
|
||||||
|
assert.include(foxSounds, 'wah');
|
||||||
|
assert.include(foxSounds, 'tjoef');
|
||||||
|
assert.include(foxSounds, 'kek');
|
||||||
|
});
|
||||||
|
it('uses a double nested key correctly', function(){
|
||||||
|
let obj = getStartingObject();
|
||||||
|
let birdSounds = [];
|
||||||
|
applyFnToKey(obj, 'birds.$.sound.$', (doc, key) => {
|
||||||
|
birdSounds.push(get(doc, key));
|
||||||
|
});
|
||||||
|
assert.include(birdSounds, 'koer');
|
||||||
|
assert.include(birdSounds, 'hello');
|
||||||
|
assert.include(birdSounds, 'squawk');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getStartingObject(){
|
||||||
|
return {
|
||||||
|
fox: {
|
||||||
|
name: 'foxy',
|
||||||
|
sound: [
|
||||||
|
'tjoef',
|
||||||
|
'kek',
|
||||||
|
'wah'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
birds: [{
|
||||||
|
name: 'pigeon',
|
||||||
|
sound: [
|
||||||
|
'koer',
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
name: 'parrot',
|
||||||
|
sound: [
|
||||||
|
'hello',
|
||||||
|
'cracker?',
|
||||||
|
'squawk',
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from '/imports/api/properties/subSchemas/ResourcesSchema.js';
|
} from '/imports/api/properties/subSchemas/ResourcesSchema.js';
|
||||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
SimpleSchema.extendOptions(['parseLevel']);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Actions are things a character can do
|
* Actions are things a character can do
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
|
||||||
// Get schemas that apply fields directly so they can be gracefully extended
|
// Get schemas that apply fields directly so they can be gracefully extended
|
||||||
@@ -26,18 +26,36 @@ function computedOnlyInlineCalculationField(field){
|
|||||||
type: Object,
|
type: Object,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
[`${field}.value`]: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
max: STORAGE_LIMITS.inlineCalculationField,
|
||||||
|
},
|
||||||
[`${field}.inlineCalculations`]: {
|
[`${field}.inlineCalculations`]: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||||
},
|
},
|
||||||
[`${field}.inlineCalculations.$`]: {
|
[`${field}.inlineCalculations.$`]: {
|
||||||
type: InlineComputationSchema,
|
type: Object,
|
||||||
},
|
},
|
||||||
[`${field}.value`]: {
|
// The part between bracers {}
|
||||||
|
[`${field}.inlineCalculations.$.calculation`]: {
|
||||||
type: String,
|
type: String,
|
||||||
|
max: STORAGE_LIMITS.calculation,
|
||||||
|
},
|
||||||
|
[`${field}.inlineCalculations.$.value`]: {
|
||||||
|
type: SimpleSchema.oneOf(String, Number),
|
||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.inlineCalculationField,
|
max: STORAGE_LIMITS.calculation,
|
||||||
|
},
|
||||||
|
[`${field}.inlineCalculations.$.errors`]: {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
maxCount: STORAGE_LIMITS.errorCount,
|
||||||
|
},
|
||||||
|
[`${field}.inlineCalculations.$.errors.$`]: {
|
||||||
|
type: ErrorSchema,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ const exampleAction = {
|
|||||||
'usesUsed':0,
|
'usesUsed':0,
|
||||||
'description':'Starting at 1st level, you gain the ability to place a baleful curse on someone. As a bonus action, choose one creature you can see within 30 feet of you. The target is cursed for 1 minute. The curse ends early if the target dies, you die, or you are incapacitated. Until the curse ends, you gain the following benefits:\n\n- You gain a bonus to damage rolls against the cursed target. The bonus equals your proficiency bonus.\n- Any attack roll you make against the cursed target is a critical hit on a roll of 19 or 20 on the d20.\n- If the cursed target dies, you regain hit points equal to your warlock level + your Charisma modifier (minimum of 1 hit point). \n{warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."} \nYou can\\\'t use this feature again until you finish a short or long rest.',
|
'description':'Starting at 1st level, you gain the ability to place a baleful curse on someone. As a bonus action, choose one creature you can see within 30 feet of you. The target is cursed for 1 minute. The curse ends early if the target dies, you die, or you are incapacitated. Until the curse ends, you gain the following benefits:\n\n- You gain a bonus to damage rolls against the cursed target. The bonus equals your proficiency bonus.\n- Any attack roll you make against the cursed target is a critical hit on a roll of 19 or 20 on the d20.\n- If the cursed target dies, you regain hit points equal to your warlock level + your Charisma modifier (minimum of 1 hit point). \n{warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."} \nYou can\\\'t use this feature again until you finish a short or long rest.',
|
||||||
'color':'#8e24aa',
|
'color':'#8e24aa',
|
||||||
'dependencies':[
|
|
||||||
'4eM4YkgAaoCJfCfQ8',
|
|
||||||
],
|
|
||||||
'descriptionCalculations':[
|
'descriptionCalculations':[
|
||||||
{
|
{
|
||||||
'calculation':'warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
|
'calculation':'warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue';
|
|||||||
import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/getParentRefByTag.js';
|
import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/getParentRefByTag.js';
|
||||||
import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js';
|
import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js';
|
||||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||||
import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js';
|
import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
|||||||
import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue';
|
import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||||
import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js';
|
import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyV
|
|||||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||||
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
||||||
import adjustQuantity from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
import adjustQuantity from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||||
import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js';
|
import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components:{
|
components:{
|
||||||
|
|||||||
Reference in New Issue
Block a user