Changed aggregation schema of computed fields

This commit is contained in:
ThaumRystra
2023-11-01 11:12:18 +02:00
parent 243684d206
commit 6ce7542c4b
7 changed files with 176 additions and 58 deletions

View File

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

View File

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

View File

@@ -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 = [];

View File

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

View File

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

View File

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

View File

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