Got tests running on single property character

This commit is contained in:
Stefan Zermatten
2021-09-15 15:15:18 +02:00
parent 856fc41429
commit dfd7ad4af5
24 changed files with 277 additions and 119 deletions

View File

@@ -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){

View File

@@ -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);
});
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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){

View File

@@ -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);
});
} }
}); });
} }

View File

@@ -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)
});
});
}

View File

@@ -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];
}

View File

@@ -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}'
}
}), }),
]; ];

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 => {

View File

@@ -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
);
} }

View File

@@ -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);
}

View File

@@ -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],

View File

@@ -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',
]
}]
}
}

View File

@@ -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

View File

@@ -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,
}, },
}); });
} }

View File

@@ -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."',

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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:{