Substantial progress on rebuilding computation engine
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdown.js';
|
||||
|
||||
export default function computeInactiveStatus(node){
|
||||
const prop = node.node;
|
||||
if (isActive(prop)) return;
|
||||
prop.inactive = true;
|
||||
prop.deactivatedBySelf = true;
|
||||
// Mark children as inactive due to ancestor
|
||||
walkDown(node.children, child => {
|
||||
child.node.inactive = true;
|
||||
child.node.deactivatedByAncestor = true;
|
||||
});
|
||||
}
|
||||
|
||||
function isActive(prop){
|
||||
if (prop.disabled) return false;
|
||||
switch (prop.type){
|
||||
case 'buff': return !!prop.applied;
|
||||
case 'item': return !!prop.equipped;
|
||||
case 'spell': return !!prop.prepared || !!prop.alwaysPrepared;
|
||||
case 'note': return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Performs a depth first traversal of the character tree, summing the container
|
||||
* and inventory contents on the way up the tree
|
||||
*/
|
||||
export default function computeInventory(forest, dependencyGraph){
|
||||
const data = {
|
||||
weightTotal: 0,
|
||||
weightEquipment: 0,
|
||||
weightCarried: 0,
|
||||
valueTotal: 0,
|
||||
valueEquipment: 0,
|
||||
valueCarried: 0,
|
||||
itemsAttuned: 0,
|
||||
};
|
||||
// The stack of properties to still navigate
|
||||
const stack = [...forest];
|
||||
// The current containers we are inside of
|
||||
const containerStack = [];
|
||||
|
||||
while(stack.length){
|
||||
const top = stack[stack.length - 1];
|
||||
const prop = top.node;
|
||||
if (prop._computationDetails.inventoryChildrenVisited){
|
||||
stack.pop();
|
||||
handleProp(prop, containerStack, data, dependencyGraph);
|
||||
} else {
|
||||
// Add all containers to the stack when we first visit them
|
||||
if (prop.type === 'container'){
|
||||
containerStack.push(top.node);
|
||||
setDefaultContainerData(prop);
|
||||
}
|
||||
// Push children onto the stack and mark this as children are visited
|
||||
stack.push(...top.children);
|
||||
prop._computationDetails.inventoryChildrenVisited = true;
|
||||
}
|
||||
}
|
||||
// Store all the computed values on the dependency graph variables
|
||||
for (let key in data){
|
||||
dependencyGraph.addNode(key, {engineValue: data[key]});
|
||||
}
|
||||
}
|
||||
|
||||
function setDefaultContainerData(container){
|
||||
container.contentsWeight = 0;
|
||||
container.carriedWeight = 0;
|
||||
container.contentsValue = 0;
|
||||
container.carriedValue = 0;
|
||||
}
|
||||
|
||||
function handleProp(prop, containerStack, data, dependencyGraph){
|
||||
// Determine if this property is carried, items are carried by default
|
||||
let carried = prop.type === 'container' ? prop.carried : true;
|
||||
|
||||
// Weight and value for this property
|
||||
const weight = (prop.weight || 0) + (prop.contentsWeight || 0);
|
||||
const carriedWeight = (prop.weight || 0) + (prop.carriedWeight || 0);
|
||||
const value = (prop.value || 0) + (prop.value || 0);
|
||||
const carriedValue = (prop.value || 0) + (prop.carriedValue || 0);
|
||||
|
||||
// Sum the item-specific data
|
||||
if (prop.type === 'item'){
|
||||
dependencyGraph.addLink('itemsAttuned', prop._id);
|
||||
if (prop.attuned) data.itemsAttuned += 1;
|
||||
if (prop.equipped){
|
||||
dependencyGraph.addLink('weightEquipment', prop._id);
|
||||
data.weightEquipment += weight;
|
||||
dependencyGraph.addLink('valueEquipment', prop._id);
|
||||
data.valueEquipment += value;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the parent container
|
||||
const container = containerStack[containerStack.length - 1];
|
||||
|
||||
if (container){
|
||||
// The container depends on this prop for its contents data
|
||||
dependencyGraph.addLink(container._id, prop._id);
|
||||
// Add this property's weights and values to the container
|
||||
if (!container.weightless){
|
||||
container.contentsWeight += weight;
|
||||
if (carried) container.carriedWeight += carriedWeight;
|
||||
}
|
||||
container.contentsValue += value;
|
||||
if (carried) container.carriedValue += carriedValue;
|
||||
} else {
|
||||
// There is no parent container, add weights/value to the character data
|
||||
dependencyGraph.addLink('weightTotal', prop._id);
|
||||
data.weightTotal += weight;
|
||||
dependencyGraph.addLink('valueTotal', prop._id);
|
||||
data.valueTotal += value;
|
||||
if (carried){
|
||||
dependencyGraph.addLink('weightCarried', prop._id);
|
||||
data.weightCarried += carriedWeight;
|
||||
dependencyGraph.addLink('valueCarried', prop._id);
|
||||
data.valueCarried += carriedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Only computes `totalFilled`, need to compute `quantityExpected.value`
|
||||
* before `spacesLeft` can be computed
|
||||
*/
|
||||
export default function computeSlotQuantityFilled(node, dependencyGraph){
|
||||
let slot = node.node;
|
||||
slot.totalFilled = 0;
|
||||
node.children.forEach(child => {
|
||||
let childProp = child.node;
|
||||
dependencyGraph.addLink(slot._id, childProp._id)
|
||||
if (childProp.type === 'slotFiller'){
|
||||
slot.totalFilled += child.slotQuantityFilled;
|
||||
} else {
|
||||
slot.totalFilled++;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdown.js';
|
||||
|
||||
export default function computeToggleDependencies(node, dependencyGraph){
|
||||
const prop = node.node;
|
||||
// Only for toggles that aren't inactive and aren't set to enabled or disabled
|
||||
if (
|
||||
prop.inactive ||
|
||||
prop.type !== 'toggle' ||
|
||||
prop.disabled ||
|
||||
prop.enabled
|
||||
) return;
|
||||
walkDown(node.children, child => {
|
||||
child.node._computationDetails.toggleAncestors.push(prop._id);
|
||||
dependencyGraph.addLink(child.node._id, prop._id, prop.condition);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
|
||||
import findAncestorByType from 'imports/api/creature/computation/newEngine/utility/findAncestorByType.js';
|
||||
|
||||
export default function linkCalculationDependencies(dependencyGraph, prop, propsById){
|
||||
prop._computationDetails.calculations.forEach(calcObj => {
|
||||
// Traverse the parsed calculation looking for variable names
|
||||
calcObj._parsedCalculation.travese(node => {
|
||||
if (node instanceof SymbolNode || node instanceof AccessorNode){
|
||||
// Link ancestor references as direct property dependencies
|
||||
if (node.name[0] === '#'){
|
||||
let ancestorProp = findAncestorByType(
|
||||
prop, node.name.slice(1), propsById
|
||||
);
|
||||
if (!ancestorProp) return;
|
||||
dependencyGraph.addLink(prop._id, ancestorProp._id, calcObj);
|
||||
} else {
|
||||
// Link variable name references as variable dependencies
|
||||
dependencyGraph.addLink(prop._id, node.name, calcObj);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
const linkDependenciesByType = {
|
||||
attribute: linkVariableName,
|
||||
classLevel: linkVariableName,
|
||||
constant: linkVariableName,
|
||||
damageMultiplier: linkDamageMultiplier,
|
||||
proficiency: linkStats,
|
||||
effect: linkStats,
|
||||
skill: linkSkill,
|
||||
}
|
||||
|
||||
export default function linkTypeDependencies(dependencyGraph, prop){
|
||||
linkDependenciesByType[prop.type]?.(prop);
|
||||
}
|
||||
|
||||
function linkVariableName(dependencyGraph, prop){
|
||||
// The variableName of the prop depends on the prop
|
||||
if (prop.variableName){
|
||||
dependencyGraph.addLink(prop.variableName, prop._id);
|
||||
}
|
||||
}
|
||||
|
||||
function linkDamageMultiplier(dependencyGraph, prop){
|
||||
prop.damageTypes.forEach(damageType => {
|
||||
dependencyGraph.addLink(`${damageType}Multiplier`, prop._id);
|
||||
});
|
||||
}
|
||||
|
||||
function linkStats(dependencyGraph, prop){
|
||||
// The stats a prop references depend on that prop
|
||||
prop.stats.forEach(variableName => {
|
||||
if (!variableName) return;
|
||||
dependencyGraph.addLink(variableName, prop._id);
|
||||
});
|
||||
}
|
||||
|
||||
function linkSkill(dependencyGraph, prop){
|
||||
linkVariableName(dependencyGraph, prop);
|
||||
// The prop depends on the variable references as the ability
|
||||
if (prop.ability) dependencyGraph.addLink(prop._id, prop.ability);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js';
|
||||
|
||||
export default function parseCalculationFields(prop, schemas){
|
||||
// For each key in the schema
|
||||
schemas[prop.type]._schemaKeys.forEach( key => {
|
||||
// that ends in '.calculation'
|
||||
if (key.slice(-12) !== '.calculation') return;
|
||||
const calcKey = key.sclice(0, -12);
|
||||
|
||||
// For all fields matching they keys
|
||||
// supports `keys.$.with.$.arrays`
|
||||
applyFnToKey(prop, calcKey, calcObj => {
|
||||
// Store a reference to all the calculations
|
||||
prop._computationDetails.calculations.push(calcObj);
|
||||
// Parse the calculation
|
||||
parseCalculation(calcObj);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function parseCalculation(calcObj){
|
||||
let calculation = calcObj.calculation || '';
|
||||
try {
|
||||
calcObj._parsedCalculation = parse(calculation);
|
||||
} catch (e) {
|
||||
let error = prettifyParseError(e);
|
||||
calcObj.errors ?
|
||||
calcObj.errors.push(error) :
|
||||
calcObj.errors = [error];
|
||||
calcObj._parsedCalculation = new ErrorNode({error});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
import CreatureProperties,
|
||||
{ DenormalisedOnlyCreaturePropertySchema as denormSchema }
|
||||
from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
||||
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js';
|
||||
import { cloneDeep, unset } from 'lodash';
|
||||
import createGraph from 'ngraph.graph';
|
||||
import computeInventory from '/imports/api/creature/computation/newEngine/buildComputation/computeInventory.js';
|
||||
import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdown.js';
|
||||
import parseCalculationFields from '/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js';
|
||||
import computeInactiveStatus from '/imports/api/creature/computation/newEngine/buildComputation/computeInactiveStatus.js';
|
||||
import computeToggleDependencies from '/imports/api/creature/computation/newEngine/buildComputation/computeToggleDependencies.js';
|
||||
import linkCalculationDependencies from '/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js';
|
||||
import linkTypeDependencies from '/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js';
|
||||
import computeSlotQuantityFilled from '/imports/api/creature/computation/newEngine/buildComputation/computeSlotQuantityFilled.js';
|
||||
|
||||
/**
|
||||
* Store index of properties
|
||||
* recompute static tree-based enabled/disabled status
|
||||
* Build a dependency graph
|
||||
* id -> id dependencies for docs that rely on other docs directly
|
||||
* id -> variable deps for docs that rely on a variable's value
|
||||
* variable -> id deps for variables that are impacted by docs
|
||||
* TODO:
|
||||
* Depth first traversal or dependency graph to:
|
||||
* Find loops in the dependency graph
|
||||
* resolve variables in dependency order
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* compute slots spaces left (after computed field of quantityExpected)
|
||||
* compute damage multipliers
|
||||
* compute dependencyGraph variables and properties
|
||||
*/
|
||||
|
||||
export default function buildCreatureComputation(creatureId){
|
||||
let properties = CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
'removed': {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
|
||||
// Dependency graph where edge(a, b) means a depends on b
|
||||
// The graph includes all dependencies even of inactive properties
|
||||
// such that any properties changing without changing their dependencies
|
||||
// can limit the recompute to connected parts of the graph
|
||||
const dependencyGraph = createGraph();
|
||||
|
||||
const computation = {
|
||||
originalPropsById: {},
|
||||
propsById: {},
|
||||
propsByType: {},
|
||||
propsByVariableName: {},
|
||||
props: properties,
|
||||
dependencyGraph,
|
||||
};
|
||||
|
||||
// Process the properties one by one
|
||||
properties.forEach(prop => {
|
||||
|
||||
// Store the prop in the memo by type, variableName and id
|
||||
storePropInMemo(prop, computation)
|
||||
|
||||
// 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
|
||||
prop._computationDetails = {
|
||||
calculations: [],
|
||||
toggleAncestors: [],
|
||||
};
|
||||
|
||||
// Parse all the calculations
|
||||
parseCalculationFields(prop, computedSchemas)
|
||||
});
|
||||
|
||||
// Get all the properties as trees based on their ancestors
|
||||
let forest = nodeArrayToTree(properties);
|
||||
// Walk the property trees computing things that need to be inherited
|
||||
walkDown(forest, node => {
|
||||
computeInactiveStatus(node);
|
||||
computeToggleDependencies(node);
|
||||
computeSlotQuantityFilled(node);
|
||||
});
|
||||
|
||||
// Compute the inventory
|
||||
computeInventory(forest, dependencyGraph);
|
||||
|
||||
// Graph functions that rely on the props being stored first
|
||||
properties.forEach(prop => {
|
||||
linkTypeDependencies(dependencyGraph, prop, computation.propsById);
|
||||
linkCalculationDependencies(dependencyGraph, prop, computation.propsById);
|
||||
});
|
||||
|
||||
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];
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
import CreatureProperties,
|
||||
{ DenormalisedOnlyCreaturePropertySchema as denormSchema }
|
||||
from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
||||
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/creature/computation/newEngine/applyFnToKey.js';
|
||||
import { cloneDeep, unset } from 'lodash';
|
||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
|
||||
import createGraph from 'ngraph.graph';
|
||||
import findAncestorByType from 'imports/api/creature/computation/newEngine/findAncestorByType.js';
|
||||
|
||||
/**
|
||||
* Store index of properties
|
||||
* recompute static tree-based enabled/disabled status
|
||||
* Build a dependency graph
|
||||
* id -> id dependencies for docs that rely on other docs directly
|
||||
* id -> variable deps for docs that rely on a variable's value
|
||||
* TODO:
|
||||
* variable -> id deps for variables that are impacted by docs
|
||||
* Depth first traversal or dependency graph to:
|
||||
* Find loops in the dependency graph
|
||||
* resolve variables in dependency order
|
||||
*/
|
||||
|
||||
export default function computeCreature(creatureId){
|
||||
let properties = CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
'removed': {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
|
||||
const originalPropsById = {};
|
||||
const propsById = {};
|
||||
const propsByType = {};
|
||||
|
||||
// Process the properties one by one
|
||||
properties.forEach(prop => {
|
||||
// Store the prop by Id and Type
|
||||
originalPropsById[prop._id] = cloneDeep(prop);
|
||||
propsById[prop._id] = prop;
|
||||
if (!propsByType[prop.type]) propsByType[prop.type] = [];
|
||||
propsByType[prop.type].push(prop);
|
||||
|
||||
// Store the prop in the dependency graph
|
||||
dependencyGraph.addNode(prop._id, prop);
|
||||
|
||||
// Remove all computed only fields
|
||||
computedOnlySchemas[prop.type]._schemaKeys.forEach(key =>
|
||||
applyFnToKey(prop, key, unset)
|
||||
);
|
||||
|
||||
// Remove all denormalised fields
|
||||
denormSchema._schemaKeys.forEach(key =>
|
||||
applyFnToKey(prop, key, unset)
|
||||
);
|
||||
|
||||
// Add a place to store all the computation details
|
||||
prop._computationDetails = {
|
||||
calculations: [],
|
||||
toggleAncestors: [],
|
||||
};
|
||||
|
||||
// parse every calculation field
|
||||
computedSchemas[prop.type]._schemaKeys.forEach( key => {
|
||||
if (key.slice(-11) !== 'calculation') return;
|
||||
const calcKey = key.sclice(0, -11);
|
||||
applyFnToKey(prop, calcKey, calcObj => {
|
||||
// Store a reference to all the calculations
|
||||
prop._computationDetails.calculations.push(calcObj);
|
||||
// Parse the calculation
|
||||
parseCalculation(calcObj);
|
||||
return calcObj;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Process the properties in tree format
|
||||
let creatureTree = nodeArrayToTree(properties);
|
||||
walkDown(creatureTree, node => {
|
||||
denormaliseInactiveStatus(node);
|
||||
inheritToggleDependencies(node);
|
||||
computeInventory(node);
|
||||
});
|
||||
|
||||
// Dependency graph where edge(a, b) means a depends on b
|
||||
const dependencyGraph = createGraph();
|
||||
// Build graph now that all props are stored
|
||||
properties.forEach(prop => {
|
||||
linkTypeDependencies(dependencyGraph, prop, propsById);
|
||||
if (prop.inactive) return;
|
||||
linkCalculationDependencies(dependencyGraph, prop, propsById);
|
||||
});
|
||||
}
|
||||
|
||||
function walkDown(tree, callback){
|
||||
let stack = [...tree];
|
||||
while(stack.length){
|
||||
let node = stack.pop();
|
||||
callback(node);
|
||||
stack.push(...node.children);
|
||||
}
|
||||
}
|
||||
|
||||
function denormaliseInactiveStatus(node){
|
||||
const prop = node.node;
|
||||
if (isActive(prop)) return;
|
||||
prop.inactive = true;
|
||||
prop.deactivatedBySelf = true;
|
||||
// Mark children as inactive due to ancestor
|
||||
walkDown(node.children, child => {
|
||||
child.node.inactive = true;
|
||||
child.node.deactivatedByAncestor = true;
|
||||
});
|
||||
}
|
||||
|
||||
function isActive(prop){
|
||||
if (prop.disabled) return false;
|
||||
switch (prop.type){
|
||||
case 'buff': return !!prop.applied;
|
||||
case 'item': return !!prop.equipped;
|
||||
case 'spell': return !!prop.prepared || !!prop.alwaysPrepared;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
function inheritToggleDependencies(node, dependencyGraph){
|
||||
const prop = node.node;
|
||||
// Only for toggles that aren't inactive and aren't set to enabled or disabled
|
||||
if (
|
||||
prop.inactive ||
|
||||
prop.type !== 'toggle' ||
|
||||
prop.disabled ||
|
||||
prop.enabled
|
||||
) return;
|
||||
walkDown(node.children, child => {
|
||||
child.node._computationDetails.toggleAncestors.push(prop._id);
|
||||
dependencyGraph.addLink(child.node._id, prop._id, prop.condition);
|
||||
});
|
||||
}
|
||||
|
||||
function computeInventory(forest){
|
||||
const data = {
|
||||
weightTotal: 0,
|
||||
weightEquipment: 0,
|
||||
weightCarried: 0,
|
||||
valueTotal: 0,
|
||||
valueEquipment: 0,
|
||||
valueCarried: 0,
|
||||
itemsAttuned: 0,
|
||||
}
|
||||
// The stack of properties to still navigate
|
||||
const stack = [...forest];
|
||||
// The current containers we are inside of
|
||||
const containerStack = [];
|
||||
const visitedNodes = new Set();
|
||||
|
||||
while(stack.length){
|
||||
const top = stack[stack.length - 1];
|
||||
// Leaf node
|
||||
if (top.children.length === 0){
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseCalculation(calcObj){
|
||||
let calculation = calcObj.calculation || '';
|
||||
try {
|
||||
calcObj._parsedCalculation = parse(calculation);
|
||||
} catch (e) {
|
||||
let error = prettifyParseError(e);
|
||||
calcObj.errors ?
|
||||
calcObj.errors.push(error) :
|
||||
calcObj.errors = [error];
|
||||
calcObj._parsedCalculation = new ErrorNode({error});
|
||||
}
|
||||
}
|
||||
|
||||
function linkCalculationDependencies(dependencyGraph, prop, propsById){
|
||||
let variableNames = [];
|
||||
prop._computationDetails.calculations.forEach(calcObj => {
|
||||
calcObj._parsedCalculation.travese(node => {
|
||||
if (node instanceof SymbolNode || node instanceof AccessorNode){
|
||||
if (node.name[0] !== '#'){
|
||||
dependencyGraph.addLink(prop._id, node.name, calcObj);
|
||||
} else {
|
||||
let ancestorProp = findAncestorByType(
|
||||
prop, node.name.slice(1), propsById
|
||||
);
|
||||
if (!ancestorProp) return;
|
||||
dependencyGraph.addLink(prop._id, ancestorProp._id, calcObj);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return variableNames;
|
||||
}
|
||||
|
||||
const inventoryVariables = [
|
||||
'weightTotal',
|
||||
'weightEquipment',
|
||||
'weightCarried',
|
||||
'valueTotal',
|
||||
'valueEquipment',
|
||||
'valueCarried',
|
||||
'itemsAttuned',
|
||||
];
|
||||
|
||||
const linkDependenciesByType = {
|
||||
attribute: linkVariableName,
|
||||
classLevel: linkVariableName,
|
||||
constant: linkVariableName,
|
||||
container: linkInventoryVariables,
|
||||
}
|
||||
|
||||
function linkVariableName(dependencyGraph, prop){
|
||||
if (prop.inactive) return;
|
||||
if (prop.variableName){
|
||||
dependencyGraph.addLink(prop.variableName, prop._id);
|
||||
}
|
||||
}
|
||||
|
||||
function linkInventoryVariables(dependencyGraph, prop){
|
||||
inventoryVariables.forEach(variableName => {
|
||||
dependencyGraph.addLink(variableName, prop._id);
|
||||
});
|
||||
}
|
||||
|
||||
function linkTypeDependencies(dependencyGraph, prop){
|
||||
linkDependenciesByType[prop.type]?.(prop);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default function walkDown(tree, callback){
|
||||
let stack = [...tree];
|
||||
while(stack.length){
|
||||
let node = stack.pop();
|
||||
callback(node);
|
||||
stack.push(...node.children);
|
||||
}
|
||||
}
|
||||
@@ -44,10 +44,20 @@ const ComputedOnlyContainerSchema = createPropertySchema({
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
// Weight of all the carried contents (some sub-containers might not be carried)
|
||||
// zero if `contentsWeightless` is true
|
||||
carriedWeight:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
contentsValue:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
carriedValue:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedContainerSchema = new SimpleSchema()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
@@ -20,7 +19,7 @@ let DamageMultiplierSchema = new SimpleSchema({
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
'damageTypes.$': {
|
||||
type: String,
|
||||
allowedValues: DAMAGE_TYPES,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// The value of the damage multiplier
|
||||
value: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const DamageSchema = createPropertySchema({
|
||||
// The roll that determines how much to damage the attribute
|
||||
@@ -22,7 +22,7 @@ const DamageSchema = createPropertySchema({
|
||||
},
|
||||
damageType: {
|
||||
type: String,
|
||||
allowedValues: DAMAGE_TYPES,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
defaultValue: 'slashing',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,7 +30,6 @@ let SlotFillerSchema = new SimpleSchema({
|
||||
slotQuantityFilled: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
min: 0,
|
||||
},
|
||||
// Filters out of UI if condition isn't met, but isn't otherwise enforced
|
||||
slotFillerCondition: {
|
||||
|
||||
Reference in New Issue
Block a user