Tore out the old engine, left some wounds
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import walkDown from '/imports/api/engine/computation/utility/walkdown.js';
|
||||
|
||||
export default function computeInactiveStatus(node){
|
||||
const prop = node.node;
|
||||
if (isActive(prop)) return;
|
||||
// Unequipped items disable their children, but are not disabled themselves
|
||||
// All notes do the same
|
||||
if (prop.type !== 'item' && prop.type !== 'note' ){
|
||||
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,21 @@
|
||||
/**
|
||||
* Only computes `totalFilled`, need to compute `quantityExpected.value`
|
||||
* before `spacesLeft` can be computed
|
||||
*/
|
||||
export default function computeSlotQuantityFilled(node, dependencyGraph){
|
||||
let slot = node.node;
|
||||
if (slot.type !== 'propertySlot') return;
|
||||
slot.totalFilled = 0;
|
||||
node.children.forEach(child => {
|
||||
let childProp = child.node;
|
||||
dependencyGraph.addLink(slot._id, childProp._id, 'slotFill');
|
||||
if (
|
||||
childProp.type === 'slotFiller' &&
|
||||
Number.isFinite(childProp.slotQuantityFilled)
|
||||
){
|
||||
slot.totalFilled += childProp.slotQuantityFilled;
|
||||
} else {
|
||||
slot.totalFilled++;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import walkDown from '/imports/api/engine/computation/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);
|
||||
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
|
||||
import findAncestorByType from '/imports/api/engine/computation/utility/findAncestorByType.js';
|
||||
|
||||
export default function linkCalculationDependencies(dependencyGraph, prop, {propsById}){
|
||||
prop._computationDetails.calculations.forEach(calcObj => {
|
||||
// Store resolved ancestors
|
||||
let memo = {
|
||||
// ancestors: {} //this gets added if there are resolved ancestors
|
||||
};
|
||||
// Traverse the parsed calculation looking for variable names
|
||||
calcObj._parsedCalculation.traverse(node => {
|
||||
// Skip nodes that aren't symbols or accessors
|
||||
if (!(node instanceof SymbolNode || node instanceof AccessorNode)) return;
|
||||
// Link ancestor references as direct property dependencies
|
||||
if (node.name[0] === '#'){
|
||||
let ancestorProp = getAncestorProp(
|
||||
node.name.slice(1), memo, prop, 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);
|
||||
}
|
||||
});
|
||||
// Store the resolved ancestors in this calculation's local scope
|
||||
if (memo.ancestors) {
|
||||
calcObj._localScope = { ...calcObj._localScope, ...memo.ancestors};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getAncestorProp(type, memo, prop, propsById){
|
||||
if (memo.ancestors && memo.ancestors['#' + type]){
|
||||
return memo.ancestors['#' + type];
|
||||
} else {
|
||||
var ancestorProp = findAncestorByType( prop, type, propsById );
|
||||
if (!memo.ancestors) memo.ancestors = {};
|
||||
memo.ancestors['#' + type] = ancestorProp;
|
||||
return ancestorProp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Performs a depth first traversal of the character tree, summing the container
|
||||
* and inventory contents on the way up the tree
|
||||
*/
|
||||
export default function linkInventory(forest, dependencyGraph){
|
||||
// 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){
|
||||
if (prop.type === 'container') containerStack.pop();
|
||||
stack.pop();
|
||||
handleProp(prop, containerStack, dependencyGraph);
|
||||
} else {
|
||||
// Add all containers to the stack when we first visit them
|
||||
if (prop.type === 'container'){
|
||||
containerStack.push(top.node);
|
||||
}
|
||||
// Push children onto the stack and mark this as children are visited
|
||||
stack.push(...top.children);
|
||||
prop._computationDetails.inventoryChildrenVisited = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleProp(prop, containerStack, dependencyGraph){
|
||||
// Skip props that aren't part of the inventory
|
||||
if (prop.type !== 'item' && prop.type !== 'container') return;
|
||||
// Determine if this property is carried, items are carried by default
|
||||
let carried = prop.type === 'container' ? prop.carried : true;
|
||||
|
||||
// Item-specific links
|
||||
if (prop.type === 'item'){
|
||||
if (prop.attuned){
|
||||
dependencyGraph.addLink('itemsAttuned', prop._id, 'attunedItem');
|
||||
}
|
||||
if (prop.equipped){
|
||||
dependencyGraph.addLink('weightEquipment', prop._id, 'equippedItem');
|
||||
dependencyGraph.addLink('valueEquipment', prop._id, 'equippedItem');
|
||||
}
|
||||
}
|
||||
|
||||
// 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, 'containerContents');
|
||||
} else {
|
||||
// There is no parent container, the character totals depend on this prop
|
||||
dependencyGraph.addLink('weightTotal', prop._id, 'inventoryStats');
|
||||
dependencyGraph.addLink('valueTotal', prop._id, 'inventoryStats');
|
||||
if (carried){
|
||||
dependencyGraph.addLink('weightCarried', prop._id, 'inventoryStats');
|
||||
dependencyGraph.addLink('valueCarried', prop._id, 'inventoryStats');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
const linkDependenciesByType = {
|
||||
action: linkResources,
|
||||
attack: linkResources,
|
||||
attribute: linkAttribute,
|
||||
class: linkVariableName,
|
||||
classLevel: linkClassLevel,
|
||||
constant: linkVariableName,
|
||||
damageMultiplier: linkDamageMultiplier,
|
||||
proficiency: linkStats,
|
||||
effect: linkStats,
|
||||
skill: linkSkill,
|
||||
spell: linkResources,
|
||||
}
|
||||
|
||||
export default function linkTypeDependencies(dependencyGraph, prop, computation){
|
||||
linkDependenciesByType[prop.type]?.(dependencyGraph, prop, computation);
|
||||
}
|
||||
|
||||
function linkClassLevel(dependencyGraph, prop){
|
||||
// The variableName of the prop depends on the prop
|
||||
if (prop.variableName && prop.level){
|
||||
dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel');
|
||||
// The level variable depends on the class variableName variable
|
||||
let existingLevelLink = dependencyGraph.getLink('level', prop.variableName);
|
||||
if (!existingLevelLink){
|
||||
dependencyGraph.addLink('level', prop.variableName, 'level');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function linkVariableName(dependencyGraph, prop){
|
||||
// The variableName of the prop depends on the prop
|
||||
if (prop.variableName){
|
||||
dependencyGraph.addLink(prop.variableName, prop._id, 'definition');
|
||||
}
|
||||
}
|
||||
|
||||
function linkResources(dependencyGraph, prop, {propsById}){
|
||||
if (!prop.resources) return;
|
||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
||||
if (!itemConsumed.itemId) return;
|
||||
const item = propsById[itemConsumed.itemId];
|
||||
if (!item || item.inactive){
|
||||
// Unlink if the item doesn't exist or is inactive
|
||||
itemConsumed.itemId = undefined;
|
||||
return;
|
||||
}
|
||||
// none of these dependencies are computed, we can use them immediately
|
||||
itemConsumed.available = item.quantity;
|
||||
itemConsumed.itemName = item.name;
|
||||
itemConsumed.itemIcon = item.icon;
|
||||
itemConsumed.itemColor = item.color;
|
||||
dependencyGraph.addLink(prop._id, item._id, 'inventory');
|
||||
});
|
||||
prop.resources.attributesConsumed.forEach(attConsumed => {
|
||||
if (!attConsumed.variableName) return;
|
||||
dependencyGraph.addLink(prop._id, attConsumed.variableName, 'resource');
|
||||
});
|
||||
}
|
||||
|
||||
function linkAttribute(dependencyGraph, prop){
|
||||
linkVariableName(dependencyGraph, prop);
|
||||
// hit dice depend on constitution
|
||||
if (prop.attributeType === 'hitDice'){
|
||||
dependencyGraph.addLink(prop._id, 'constitution', 'hitDiceConMod');
|
||||
}
|
||||
}
|
||||
|
||||
function linkDamageMultiplier(dependencyGraph, prop){
|
||||
prop.damageTypes.forEach(damageType => {
|
||||
// Remove all non-letter characters from the damage name
|
||||
const damageName = damageType.replace(/[^a-z]/gi, '')
|
||||
dependencyGraph.addLink(`${damageName}Multiplier`, prop._id, prop.type);
|
||||
});
|
||||
}
|
||||
|
||||
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, prop.type);
|
||||
});
|
||||
}
|
||||
|
||||
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, 'skillAbilityScore');
|
||||
}
|
||||
// Skills depend on the creature's proficiencyBonus
|
||||
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
|
||||
import { get } from 'lodash';
|
||||
|
||||
export default function parseCalculationFields(prop, schemas){
|
||||
discoverInlineCalculationFields(prop, schemas);
|
||||
parseAllCalculationFields(prop, schemas);
|
||||
}
|
||||
|
||||
function discoverInlineCalculationFields(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 parseAllCalculationFields(prop, schemas){
|
||||
// For each key in the schema
|
||||
schemas[prop.type]._schemaKeys.forEach( key => {
|
||||
// that ends in '.calculation'
|
||||
if (key.slice(-12) === '.calculation'){
|
||||
const calcKey = key.slice(0, -12);
|
||||
// Determine the level the calculation should compute down to
|
||||
let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel || 'reduce';
|
||||
|
||||
// For all fields matching they keys
|
||||
// supports `keys.$.with.$.arrays`
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj) return;
|
||||
// Store a reference to all the calculations
|
||||
prop._computationDetails.calculations.push(calcObj);
|
||||
// Store the level to compute down to later
|
||||
calcObj._parseLevel = parseLevel;
|
||||
// Parse the calculation
|
||||
parseCalculation(calcObj);
|
||||
});
|
||||
// Or that ends in .inlineCalculations
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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,16 @@
|
||||
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 and array keys
|
||||
if (
|
||||
schema.getQuickTypeForKey(key) === 'object' ||
|
||||
schema.getQuickTypeForKey(key) === 'objectArray'
|
||||
) return;
|
||||
// Unset other computed only keys
|
||||
applyFnToKey(prop, key, unset)
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { buildComputationFromProps } from '/imports/api/engine/computation/buildCreatureComputation.js';
|
||||
import { assert } from 'chai';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function(){
|
||||
let computation = buildComputationFromProps(testProperties);
|
||||
const bySelf = (propId, note) => assertDeactivatedBySelf(computation, propId, note);
|
||||
const byAncestor = (propId, note) => assertDeactivatedByAncestor(computation, propId, note);
|
||||
const active = (propId, note) => assertActive(computation, propId, note);
|
||||
|
||||
// Buffs
|
||||
bySelf('buffNotAppliedId');
|
||||
byAncestor('buffNotAppliedChildId');
|
||||
active('buffAppliedId');
|
||||
active('buffAppliedChildId');
|
||||
|
||||
// Items
|
||||
active('itemUnequippedId', 'Unequipped items should be active');
|
||||
byAncestor('itemUnequippedChildId', 'Children of unequipped items should be inactive');
|
||||
active('itemEquippedId');
|
||||
active('itemEquippedChildId');
|
||||
|
||||
// Spells
|
||||
active('spellPreparedId');
|
||||
active('spellPreparedChildId');
|
||||
active('spellAlwaysPreparedId');
|
||||
active('spellAlwaysPreparedChildId');
|
||||
bySelf('spellUnpreparedId');
|
||||
byAncestor('spellUnpreparedChildId');
|
||||
|
||||
// Notes
|
||||
active('NoteId', 'Notes should be active');
|
||||
byAncestor('NoteChildId', 'children of notes should always be inactive');
|
||||
}
|
||||
|
||||
function assertDeactivatedBySelf(computation, propId, note){
|
||||
const prop = computation.propsById[propId];
|
||||
assert.isTrue(prop.deactivatedBySelf, note);
|
||||
assert.isTrue(prop.inactive, 'The property should be inactive');
|
||||
}
|
||||
|
||||
function assertDeactivatedByAncestor(computation, propId, note){
|
||||
const prop = computation.propsById[propId];
|
||||
assert.isTrue(prop.deactivatedByAncestor, note);
|
||||
assert.isTrue(prop.inactive, 'The property should be inactive');
|
||||
}
|
||||
|
||||
function assertActive(computation, propId, note){
|
||||
const prop = computation.propsById[propId];
|
||||
assert.isNotTrue(prop.inactive, note);
|
||||
assert.isNotTrue(prop.deactivatedBySelf);
|
||||
assert.isNotTrue(prop.deactivatedBySelf);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
// Buffs
|
||||
clean({
|
||||
_id: 'buffNotAppliedId',
|
||||
type: 'buff',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'buffNotAppliedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'buffNotAppliedId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'buffAppliedId',
|
||||
type: 'buff',
|
||||
applied: true,
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'buffAppliedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'buffAppliedId'}],
|
||||
}),
|
||||
// Items
|
||||
clean({
|
||||
_id: 'itemUnequippedId',
|
||||
type: 'item',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'itemUnequippedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'itemUnequippedId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'itemEquippedId',
|
||||
type: 'item',
|
||||
equipped: true,
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'itemEquippedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'itemEquippedId'}],
|
||||
}),
|
||||
// Spells
|
||||
clean({
|
||||
_id: 'spellPreparedId',
|
||||
type: 'spell',
|
||||
ancestors: [{id: 'charId'}],
|
||||
prepared: true,
|
||||
}),
|
||||
clean({
|
||||
_id: 'spellPreparedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'spellPreparedId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'spellAlwaysPreparedId',
|
||||
type: 'spell',
|
||||
ancestors: [{id: 'charId'}],
|
||||
alwaysPrepared: true,
|
||||
}),
|
||||
clean({
|
||||
_id: 'spellAlwaysPreparedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'spellAlwaysPreparedId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'spellUnpreparedId',
|
||||
type: 'spell',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'spellUnpreparedChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'spellUnpreparedId'}],
|
||||
}),
|
||||
// Notes
|
||||
clean({
|
||||
_id: 'NoteId',
|
||||
type: 'note',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'NoteChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'NoteId'}],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,36 @@
|
||||
import { buildComputationFromProps } from '/imports/api/engine/computation/buildCreatureComputation.js';
|
||||
import { assert } from 'chai';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function(){
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const totalFilled = computation.propsById['slotId'].totalFilled;
|
||||
assert.equal(totalFilled, 4);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
// Slots
|
||||
clean({
|
||||
_id: 'slotId',
|
||||
type: 'propertySlot',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
// Children
|
||||
clean({
|
||||
_id: 'slotFillerId',
|
||||
type: 'slotFiller',
|
||||
slotQuantityFilled: 3,
|
||||
slotFillerType: 'item',
|
||||
ancestors: [{id: 'charId'}, {id: 'slotId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'slotChildId',
|
||||
type: 'item',
|
||||
ancestors: [{id: 'charId'}, {id: 'slotId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'slotGrandchildId',
|
||||
type: 'effect',
|
||||
ancestors: [{id: 'charId'}, {id: 'slotId'}, {id: 'slotChildId'}],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,74 @@
|
||||
import { buildComputationFromProps } from '/imports/api/engine/computation/buildCreatureComputation.js';
|
||||
import { assert } from 'chai';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function(){
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const hasLink = computation.dependencyGraph.hasLink;
|
||||
assert.include(
|
||||
computation.propsById['conditionToggleChildId']._computationDetails.toggleAncestors,
|
||||
computation.propsById['conditionToggleId'],
|
||||
'Children of toggles should store a reference to their toggle ancestor'
|
||||
)
|
||||
assert.isTrue(
|
||||
!!hasLink('conditionToggleChildId', 'conditionToggleId'),
|
||||
'Children of the condition toggle should depend on it'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('conditionToggleGrandChildId', 'conditionToggleId'),
|
||||
'Descendants of the condition toggle should depend on it'
|
||||
);
|
||||
assert.isFalse(
|
||||
!!hasLink('disabledToggleId', 'disabledToggleChildId'),
|
||||
'The dependency should not be reversed'
|
||||
);
|
||||
assert.isFalse(
|
||||
!!hasLink('disabledToggleChildId', 'disabledToggleId'),
|
||||
'Children of disabled toggle should not depend on it'
|
||||
);
|
||||
assert.isFalse(
|
||||
!!hasLink('enabledToggleChildId', 'enabledToggleId'),
|
||||
'Children of enabled toggle should not depend on it'
|
||||
);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
clean({
|
||||
_id: 'enabledToggleId',
|
||||
type: 'toggle',
|
||||
enabled: true,
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'disabledToggleId',
|
||||
type: 'toggle',
|
||||
disabled: true,
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'conditionToggleId',
|
||||
type: 'toggle',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
// Children
|
||||
clean({
|
||||
_id: 'enabledToggleChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'enabledToggleId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'disabledToggleChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'disabledToggleId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'conditionToggleChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'conditionToggleId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'conditionToggleGrandChildId',
|
||||
type: 'folder',
|
||||
ancestors: [{id: 'charId'}, {id: 'conditionToggleId'}, {id: 'conditionToggleChildId'}],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,54 @@
|
||||
import { buildComputationFromProps } from '/imports/api/engine/computation/buildCreatureComputation.js';
|
||||
import { assert } from 'chai';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function(){
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const hasLink = computation.dependencyGraph.hasLink;
|
||||
assert.isTrue(
|
||||
!!hasLink('childId', 'spellListId'),
|
||||
'Ancestor references of parent in inline calculations should create dependency'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('grandchildId', 'spellListId'),
|
||||
'References to higher ancestor should create dependency'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('grandchildId', 'strength'),
|
||||
'Variable references create dependencies'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('grandchildId', 'wisdom'),
|
||||
'Variable references create dependencies even if the attributes don\'t exist'
|
||||
);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
clean({
|
||||
_id: 'spellListId',
|
||||
type: 'spellList',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'childId',
|
||||
type: 'spell',
|
||||
description: {
|
||||
text: 'DC {#spellList.dc} save or suck'
|
||||
},
|
||||
ancestors: [{id: 'charId'}, {id: 'spellListId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'grandchildId',
|
||||
type: 'savingThrow',
|
||||
dc: {
|
||||
calculation: '#spellList.dc + strength + wisdom.modifier'
|
||||
},
|
||||
ancestors: [{id: 'charId'}, {id: 'spellListId'}, {id: 'childId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'strengthId',
|
||||
type: 'attribute',
|
||||
variableName: 'strength',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,89 @@
|
||||
import { buildComputationFromProps } from '/imports/api/engine/computation/buildCreatureComputation.js';
|
||||
import { assert } from 'chai';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function(){
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const hasLink = computation.dependencyGraph.hasLink;
|
||||
|
||||
assert.isTrue(
|
||||
!!hasLink('weightEquipment', 'equippedAttunedItemId'),
|
||||
'weight of equipment depends on equipped items'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('valueEquipment', 'equippedAttunedItemId'),
|
||||
'value of equipment depends on equipped items'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('weightTotal', 'equippedAttunedItemId'),
|
||||
'weightTotal depends on equipped items'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('valueTotal', 'equippedAttunedItemId'),
|
||||
'valueTotal depends on equipped items'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('weightCarried', 'equippedAttunedItemId'),
|
||||
'weightCarried depends on equipped items'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('valueCarried', 'equippedAttunedItemId'),
|
||||
'valueCarried depends on equipped items'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('weightCarried', 'containerId'),
|
||||
'weightCarried depends on top level containers'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('valueCarried', 'containerId'),
|
||||
'valueCarried depends on top level containers'
|
||||
);
|
||||
assert.isFalse(
|
||||
!!hasLink('weightCarried', 'childContainerId'),
|
||||
'weightCarried does not depend on nested containers'
|
||||
);
|
||||
assert.isFalse(
|
||||
!!hasLink('valueCarried', 'childContainerId'),
|
||||
'valueCarried does not depend on nested containers'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('containerId', 'childContainerId'),
|
||||
'containers depend on their child containers'
|
||||
);
|
||||
assert.isTrue(
|
||||
!!hasLink('childContainerId', 'grandchildItemId'),
|
||||
'containers depend on their child items'
|
||||
);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
clean({
|
||||
_id: 'equippedAttunedItemId',
|
||||
type: 'item',
|
||||
equipped: true,
|
||||
attuned: true,
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'containerId',
|
||||
type: 'container',
|
||||
carried: true,
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'childContainerId',
|
||||
type: 'container',
|
||||
carried: true,
|
||||
ancestors: [{id: 'charId'}, {id: 'containerId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'childItemId',
|
||||
type: 'item',
|
||||
ancestors: [{id: 'charId'}, {id: 'containerId'}],
|
||||
}),
|
||||
clean({
|
||||
_id: 'grandchildItemId',
|
||||
type: 'item',
|
||||
ancestors: [{id: 'charId'}, {id: 'containerId'}, {id: 'childContainerId'}],
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,27 @@
|
||||
import { buildComputationFromProps } from '/imports/api/engine/computation/buildCreatureComputation.js';
|
||||
import { assert } from 'chai';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function(){
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const getLink = computation.dependencyGraph.hasLink;
|
||||
const getNode = computation.dependencyGraph.getNode;
|
||||
|
||||
assert.equal(
|
||||
getLink('strength', 'strengthId').data, 'definition',
|
||||
'Links variable names to props that define them'
|
||||
);
|
||||
assert.exists(
|
||||
getNode('strength'),
|
||||
'Creates variable name nodes when attributes define them'
|
||||
);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
clean({
|
||||
_id: 'strengthId',
|
||||
type: 'attribute',
|
||||
variableName: 'strength',
|
||||
ancestors: [{id: 'charId'}],
|
||||
}),
|
||||
];
|
||||
Reference in New Issue
Block a user