Changed aggregation schema of computed fields
This commit is contained in:
@@ -39,7 +39,7 @@ export function applyTrigger(trigger, prop, actionContext) {
|
||||
// Prevent triggers from firing if their condition is false
|
||||
if (trigger.condition?.parseNode) {
|
||||
recalculateCalculation(trigger.condition, actionContext);
|
||||
if (!trigger.condition.value) return;
|
||||
if (!trigger.condition.value?.value) return;
|
||||
}
|
||||
|
||||
// Prevent triggers from firing themselves in a loop
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import call from '/imports/parser/parseTree/call.js';
|
||||
import constant from '/imports/parser/parseTree/constant.js';
|
||||
import operator from '/imports/parser/parseTree/operator.js';
|
||||
import parenthesis from '/imports/parser/parseTree/parenthesis.js';
|
||||
import { toString } from '/imports/parser/resolve.js';
|
||||
|
||||
export default function computeCalculation(computation, node) {
|
||||
const calcObj = node.data;
|
||||
evaluateCalculation(calcObj, computation.scope);
|
||||
if (calcObj.effects || calcObj.proficiencies) {
|
||||
calcObj.baseValue = calcObj.value;
|
||||
calcObj.unaffected = calcObj.value;
|
||||
calcObj.displayUnaffected = toString(calcObj.unaffected);
|
||||
}
|
||||
aggregateCalculationEffects(node, computation);
|
||||
aggregateCalculationProficiencies(node, computation);
|
||||
calcObj.displayValue = toString(calcObj.value);
|
||||
}
|
||||
|
||||
function aggregateCalculationEffects(node, computation) {
|
||||
const calcObj = node.data;
|
||||
delete calcObj.effects;
|
||||
delete calcObj.effectIds;
|
||||
computation.dependencyGraph.forEachLinkedNode(
|
||||
node.id,
|
||||
(linkedNode, link) => {
|
||||
@@ -25,29 +31,104 @@ function aggregateCalculationEffects(node, computation) {
|
||||
if (linkedNode.data.inactive) return;
|
||||
|
||||
// Collate effects
|
||||
calcObj.effects = calcObj.effects || [];
|
||||
calcObj.effects.push({
|
||||
_id: linkedNode.data._id,
|
||||
name: linkedNode.data.name,
|
||||
operation: linkedNode.data.operation,
|
||||
amount: linkedNode.data.amount && {
|
||||
value: linkedNode.data.amount.value,
|
||||
//parseNode: linkedNode.data.amount.parseNode,
|
||||
},
|
||||
// ancestors: linkedNode.data.ancestors,
|
||||
});
|
||||
calcObj.effectIds = calcObj.effectIds || [];
|
||||
calcObj.effectIds.push(linkedNode.data._id);
|
||||
},
|
||||
true // enumerate only outbound links
|
||||
);
|
||||
if (calcObj.effects && typeof calcObj.value === 'number') {
|
||||
if (calcObj.effectIds) {
|
||||
// dictionary of {[operation]: parseNode}
|
||||
const aggregator = {};
|
||||
// Store all effect values
|
||||
calcObj.effects.forEach(effect => {
|
||||
if (
|
||||
effect.operation === 'add' &&
|
||||
effect.amount && typeof effect.amount.value === 'number'
|
||||
) {
|
||||
calcObj.value += effect.amount.value
|
||||
const op = effect.operation;
|
||||
switch (op) {
|
||||
case undefined:
|
||||
break;
|
||||
// Conditionals stored as a list of text
|
||||
case 'conditional':
|
||||
if (!aggregator[op]) aggregator[op] = [];
|
||||
aggregator[op].push(effect.text);
|
||||
break;
|
||||
// Adv/Dis and Fails just count instances
|
||||
case 'advantage':
|
||||
case 'disadvantage':
|
||||
case 'fail':
|
||||
if (calcObj[op] === undefined) calcObj[op] = 0;
|
||||
calcObj[op]++;
|
||||
break;
|
||||
// Math functions store value parseNodes
|
||||
case 'base':
|
||||
case 'add':
|
||||
case 'mul':
|
||||
case 'min':
|
||||
case 'max':
|
||||
case 'set':
|
||||
if (!aggregator[op]) aggregator[op] = [];
|
||||
aggregator[op].push(effect.amount.value);
|
||||
break;
|
||||
// No case for passiveAdd, it doesn't make sense in this context
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Aggregate the effects in a parse tree like so
|
||||
* x = ( max(...base, unaffectedValue) + sum(...add) ) * mul(...mul)
|
||||
* min(...min, x)
|
||||
* max(...max, x)
|
||||
* set(last(...set))a
|
||||
*/
|
||||
// Set
|
||||
// If we do set, return early, nothing else matters
|
||||
if (aggregator.set) {
|
||||
calcObj.value = aggregator.set[aggregator.set.length - 1];
|
||||
return;
|
||||
}
|
||||
// Base value
|
||||
if (aggregator.base) {
|
||||
calcObj.value = call.create({
|
||||
functionName: 'max',
|
||||
args: [calcObj.value, aggregator.base]
|
||||
});
|
||||
}
|
||||
// Add
|
||||
aggregator.add?.forEach(node => {
|
||||
calcObj.value = operator.create({
|
||||
left: calcObj.value,
|
||||
right: node,
|
||||
operator: '+'
|
||||
});
|
||||
});
|
||||
// Multiply
|
||||
if (aggregator.mul) {
|
||||
// Wrap the previous node in brackets if it's another operator
|
||||
if (calcObj.parseType === 'operator') {
|
||||
calcObj.value = parenthesis.create({
|
||||
content: calcObj.value
|
||||
});
|
||||
}
|
||||
// Append all multiplications
|
||||
aggregator.mul.forEach(node => {
|
||||
calcObj.value = operator.create({
|
||||
left: calcObj.value,
|
||||
right: node,
|
||||
operator: '*'
|
||||
});
|
||||
});
|
||||
}
|
||||
// Min
|
||||
if (aggregator.min) {
|
||||
calcObj.value = call.create({
|
||||
functionName: 'max',
|
||||
args: [calcObj.value, aggregator.min]
|
||||
});
|
||||
}
|
||||
// Max
|
||||
if (aggregator.max) {
|
||||
calcObj.value = call.create({
|
||||
functionName: 'min',
|
||||
args: [calcObj.value, aggregator.max]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +191,10 @@ function aggregateCalculationProficiencies(node, computation) {
|
||||
prof.overridden = true;
|
||||
}
|
||||
});
|
||||
calcObj.value += calcObj.proficiencyBonus;
|
||||
calcObj.value = operator.create({
|
||||
left: calcObj.value,
|
||||
right: constant.create({ value: calcObj.proficiencyBonus }),
|
||||
operator: '+'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
export default function aggregateDefinition({node, linkedNode, link}){
|
||||
export default function aggregateDefinition({ node, linkedNode, link }) {
|
||||
// Look at all definition links
|
||||
if (link.data !== 'definition') return;
|
||||
|
||||
@@ -24,7 +24,14 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
||||
}
|
||||
|
||||
// Aggregate the base value due to the defining properties
|
||||
let propBaseValue = prop.baseValue?.value;
|
||||
let propBaseValue = undefined;
|
||||
const valueNode = prop.baseValue?.value;
|
||||
if (
|
||||
valueNode?.parseType === 'constant'
|
||||
&& valueNode?.valueType === 'number'
|
||||
) {
|
||||
propBaseValue = valueNode.value;
|
||||
}
|
||||
// Point buy rows use prop.value instead of prop.baseValue
|
||||
if (prop.type === 'pointBuyRow') {
|
||||
propBaseValue = prop.value;
|
||||
@@ -38,7 +45,7 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
||||
_id: prop.tableId,
|
||||
name: prop.tableName,
|
||||
operation: 'base',
|
||||
amount: { value: propBaseValue },
|
||||
amount: propBaseValue,
|
||||
type: 'pointBuy',
|
||||
});
|
||||
} else {
|
||||
@@ -46,16 +53,16 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
||||
_id: prop._id,
|
||||
name: prop.name,
|
||||
operation: 'base',
|
||||
amount: { value: propBaseValue },
|
||||
amount: propBaseValue,
|
||||
type: prop.type,
|
||||
});
|
||||
}
|
||||
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
||||
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue) {
|
||||
node.data.baseValue = propBaseValue;
|
||||
}
|
||||
}
|
||||
|
||||
function overrideProp(prop, node){
|
||||
function overrideProp(prop, node) {
|
||||
if (!prop) return;
|
||||
prop.overridden = true;
|
||||
if (!node.data.overriddenProps) node.data.overriddenProps = [];
|
||||
|
||||
@@ -22,21 +22,11 @@ export default function aggregateEffect({ node, linkedNode, link }) {
|
||||
// Store a summary of the effect itself
|
||||
node.data.effects = node.data.effects || [];
|
||||
// Store either just
|
||||
let effectAmount;
|
||||
if (!linkedNode.data.amount) {
|
||||
effectAmount = undefined;
|
||||
} else if (typeof linkedNode.data.amount.value === 'string') {
|
||||
effectAmount = pick(linkedNode.data.amount, [
|
||||
'calculation', 'parseNode', 'parseError', 'value'
|
||||
]);
|
||||
} else {
|
||||
effectAmount = pick(linkedNode.data.amount, ['value']);
|
||||
}
|
||||
node.data.effects.push({
|
||||
_id: linkedNode.data._id,
|
||||
name: linkedNode.data.name,
|
||||
operation: linkedNode.data.operation,
|
||||
amount: effectAmount,
|
||||
amount: linkedNode.data.amount.displayValue,
|
||||
type: linkedNode.data.type,
|
||||
text: linkedNode.data.text,
|
||||
// ancestors: linkedNode.data.ancestors,
|
||||
@@ -45,7 +35,14 @@ export default function aggregateEffect({ node, linkedNode, link }) {
|
||||
// get a shorter reference to the aggregator document
|
||||
const aggregator = node.data.effectAggregator;
|
||||
// Get the result of the effect
|
||||
let result = linkedNode.data.amount?.value;
|
||||
let result = undefined;
|
||||
const valueNode = linkedNode.data.amount?.value;
|
||||
if (
|
||||
valueNode?.parseType === 'constant'
|
||||
&& valueNode?.valueType === 'number'
|
||||
) {
|
||||
result = valueNode.value;
|
||||
}
|
||||
if (typeof result !== 'number') result = undefined;
|
||||
|
||||
// Aggregate the effect based on its operation
|
||||
|
||||
@@ -6,13 +6,8 @@ export default function evaluateCalculation(calculation, scope, givenContext) {
|
||||
const calculationScope = { ...calculation._localScope, ...scope };
|
||||
const { result: resultNode, context } = resolve(fn, parseNode, calculationScope, givenContext);
|
||||
calculation.errors = context.errors;
|
||||
if (resultNode?.parseType === 'constant') {
|
||||
calculation.value = resultNode.value;
|
||||
} else if (resultNode?.parseType === 'error') {
|
||||
calculation.value = null;
|
||||
} else {
|
||||
calculation.value = toString(resultNode);
|
||||
}
|
||||
calculation.value = resultNode;
|
||||
calculation.displayValue = toString(resultNode);
|
||||
// remove the working fields
|
||||
delete calculation._parseLevel;
|
||||
delete calculation._localScope;
|
||||
|
||||
@@ -25,28 +25,24 @@ function computedOnlyField(field) {
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
/*
|
||||
// toString(.baseValue)
|
||||
[`${field}.baseValueString`]: {
|
||||
// toString(.unaffected)
|
||||
[`${field}.displayUnaffected`]: {
|
||||
type: SimpleSchema.oneOf(String, Number),
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
*/
|
||||
// The compiled parseNode after applying all effects
|
||||
[`${field}.value`]: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
/*
|
||||
// toString(.value)
|
||||
[`${field}.valueString`]: {
|
||||
// The displayed value of the calculation: toString(.value)
|
||||
[`${field}.displayValue`]: {
|
||||
type: SimpleSchema.oneOf(String, Number),
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
*/
|
||||
// A list of effect Ids targeting this calculation
|
||||
[`${field}.effectIds`]: {
|
||||
type: Array,
|
||||
@@ -89,6 +85,30 @@ function computedOnlyField(field) {
|
||||
[`${field}.errors.$`]: {
|
||||
type: ErrorSchema,
|
||||
},
|
||||
// Effect aggregations
|
||||
[`${field}.advantage`]: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
[`${field}.disadvantage`]: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
[`${field}.fail`]: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
[`${field}.conditional`]: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
[`${field}.conditional.$`]: {
|
||||
type: String,
|
||||
},
|
||||
}
|
||||
includeParentFields(field, schemaObj);
|
||||
return new SimpleSchema(schemaObj);
|
||||
|
||||
@@ -17,13 +17,20 @@ const accessor = {
|
||||
if (value === undefined) return;
|
||||
value = value[name];
|
||||
});
|
||||
let valueType = Array.isArray(value) ? 'array' : typeof value;
|
||||
let valueType = getType(value);
|
||||
// If the accessor returns an objet, get the object's value instead
|
||||
while (valueType === 'object') {
|
||||
value = value.value;
|
||||
valueType = Array.isArray(value) ? 'array' : typeof value;
|
||||
valueType = getType(value);
|
||||
}
|
||||
// Return a parse node based on the type returned
|
||||
// Return a discovered parse node
|
||||
if (valueType === 'parseNode') {
|
||||
return {
|
||||
result: value,
|
||||
context,
|
||||
};
|
||||
}
|
||||
// Return a parse node based on the constant type returned
|
||||
if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
|
||||
return {
|
||||
result: constant.create({
|
||||
@@ -83,4 +90,11 @@ const accessor = {
|
||||
}
|
||||
}
|
||||
|
||||
function getType(val) {
|
||||
if (!val) return typeof val;
|
||||
if (Array.isArray(val)) return 'array';
|
||||
if (val.parseType) return 'parseNode';
|
||||
return typeof val;
|
||||
}
|
||||
|
||||
export default accessor;
|
||||
|
||||
Reference in New Issue
Block a user