Allowed effects and calculations to target nearest ancestors of #type

This commit is contained in:
Stefan Zermatten
2021-02-02 16:11:59 +02:00
parent 69c72e0987
commit aaa5d0b63b
13 changed files with 69 additions and 17 deletions

0
app/grammar.js Normal file
View File

View File

@@ -1,4 +1,5 @@
import { includes, cloneDeep } from 'lodash';
import findAncestorByType from '/imports/api/creature/computation/findAncestorByType.js';
// The computation memo is an in-memory data structure used only during the
// computation process
@@ -141,7 +142,7 @@ export default class ComputationMemo {
prop = this.registerProperty(prop);
let targets = this.getEffectTargets(prop);
targets.forEach(target => {
if (target.computationDetails.effects){
if (target.computationDetails && target.computationDetails.effects){
target.computationDetails.effects.push(prop);
}
});
@@ -153,7 +154,16 @@ export default class ComputationMemo {
let targets = new Set();
if (!prop.stats) return targets;
prop.stats.forEach((statName) => {
let target = this.statsByVariableName[statName];
let target;
if (statName[0] === '#'){
target = findAncestorByType({
type: statName.slice(1),
prop,
memo: this
});
} else {
target = this.statsByVariableName[statName];
}
if (!target) return;
targets.add(target);
if (isSkillOperation(prop) && isAbility(target)){

View File

@@ -8,7 +8,11 @@ export default class EffectAggregator{
result,
context,
dependencies
} = evaluateCalculation(stat.baseValueCalculation, memo);
} = evaluateCalculation({
string: stat.baseValueCalculation,
prop: stat,
memo
});
this.statBaseValue = result.value;
stat.dependencies.push(...dependencies);
if (context.errors.length){

View File

@@ -38,7 +38,11 @@ function combineAttribute(stat, aggregator, memo){
result,
context,
dependencies
} = evaluateCalculation(stat.spellSlotLevelCalculation, memo);
} = evaluateCalculation({
string: stat.spellSlotLevelCalculation,
memo,
prop: stat,
});
stat.spellSlotLevelValue = result.value;
stat.spellSlotLevelErrors = context.errors;
stat.dependencies.push(...dependencies);

View File

@@ -38,7 +38,11 @@ export default function computeEffect(effect, memo){
result,
context,
dependencies,
} = evaluateCalculation(effect.calculation, memo);
} = evaluateCalculation({
string: effect.calculation,
prop: effect,
memo
});
effect.result = result.value;
effect.dependencies.push(...dependencies);
if (context.errors.length){

View File

@@ -33,7 +33,7 @@ function computeAction(prop, memo){
result,
context,
dependencies,
} = evaluateCalculation(prop.uses, memo);
} = evaluateCalculation({ string: prop.uses, prop, memo});
prop.usesResult = result.value;
prop.dependencies.push(...dependencies);
if (context.errors.length){
@@ -85,7 +85,7 @@ function computePropertyField(prop, memo, fieldName, fn){
result,
context,
dependencies,
} = evaluateCalculation(prop[fieldName], memo, fn);
} = evaluateCalculation({string: prop[fieldName], prop, memo, fn});
if (result instanceof ConstantNode){
prop[`${fieldName}Result`] = result.value;
} else {

View File

@@ -19,7 +19,7 @@ function computeInlineCalcsForField(prop, memo, field){
result,
context,
dependencies,
} = evaluateCalculation(calculation, memo, 'compile');
} = evaluateCalculation({string: calculation, prop, memo, fn: 'compile'});
let computation = {
calculation,
result: result.toString(),

View File

@@ -30,7 +30,7 @@ export default function computeToggle(toggle, memo){
result,
context,
dependencies,
} = evaluateCalculation(toggle.condition, memo);
} = evaluateCalculation({string: toggle.condition, prop: toggle, memo});
toggle.toggleResult = !!result.value;
toggle.dependencies.push(...dependencies);
if (context.errors.length){

View File

@@ -3,9 +3,15 @@ import { parse, CompilationContext } from '/imports/parser/parser.js';
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import findAncestorByType from '/imports/api/creature/computation/findAncestorByType.js';
/* Convert a calculation into a constant output and errors*/
export default function evaluateCalculation(string, memo, fn = 'reduce'){
export default function evaluateCalculation({
string,
prop,
memo,
fn = 'reduce',
}){
let dependencies = [];
let errors = [];
if (!string) return {
@@ -38,8 +44,15 @@ export default function evaluateCalculation(string, memo, fn = 'reduce'){
// Ensure all symbol nodes are defined and computed
calc.traverse(node => {
if (node instanceof SymbolNode || node instanceof AccessorNode){
let stat = memo.statsByVariableName[node.name];
if (stat && !stat.computationDetails.computed){
// References up the tree start with $
let stat;
if (node.name[0] === '#'){
stat = findAncestorByType({type: node.name.slice(1), prop, memo});
memo.statsByVariableName[node.name] = stat;
} else {
stat = memo.statsByVariableName[node.name];
}
if (stat && stat.computationDetails && !stat.computationDetails.computed){
computeStat(stat, memo);
}
if (stat) dependencies.push(stat._id || node.name, ...stat.dependencies);

View File

@@ -0,0 +1,10 @@
export default function findAncestorByType({type, prop, memo}){
if (!prop || !prop.ancestors) return;
let ancestor;
for (let i = prop.ancestors.length - 1; i >= 0; i--){
ancestor = memo.propsById[prop.ancestors[i].id];
if (ancestor && ancestor.type === type){
return ancestor;
}
}
}

View File

@@ -24,7 +24,7 @@ function id(x) { return x[0]; }
value: s => s.slice(1, -1),
},
name: {
match: /[a-zA-Z_]*[a-ce-zA-Z_][a-zA-Z0-9_]*/,
match: /[a-zA-Z_#]*[a-ce-zA-Z_#][a-zA-Z0-9_#]*/,
type: moo.keywords({
'keywords': ['true', 'false'],
}),

View File

@@ -22,7 +22,7 @@
value: s => s.slice(1, -1),
},
name: {
match: /[a-zA-Z_]*[a-ce-zA-Z_][a-zA-Z0-9_]*/,
match: /[a-zA-Z_#]*[a-ce-zA-Z_#][a-zA-Z0-9_#]*/,
type: moo.keywords({
'keywords': ['true', 'false'],
}),

View File

@@ -7,7 +7,7 @@ export default class AccessorNode extends ParseNode {
this.name = name;
this.path = path;
}
compile(scope){
compile(scope, context){
let value = scope && scope[this.name];
// For objects, get their value
this.path.forEach(name => {
@@ -16,14 +16,21 @@ export default class AccessorNode extends ParseNode {
});
let type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean'){
return new ConstantNode({value, type, previousNodes: [this]});
return new ConstantNode({value, type});
} else if (type === 'undefined'){
return new AccessorNode({
name: this.name,
path: this.path,
});
} else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
if (context) context.storeError({
type: 'error',
message: `${this.name} returned an unexpected type`
});
return new AccessorNode({
name: this.name,
path: this.path,
});
}
}
reduce(scope, context){