Referencing a missing variable in an effect now returns zero, not an error

This commit is contained in:
Thaum Rystra
2020-05-28 19:58:52 +02:00
parent d31f980002
commit 7f2401da81
8 changed files with 198 additions and 50 deletions

View File

@@ -3,8 +3,14 @@ import evaluateCalculation from '/imports/api/creature/computation/evaluateCalcu
export default class EffectAggregator{ export default class EffectAggregator{
constructor(stat, memo){ constructor(stat, memo){
if (stat.baseValueCalculation){ if (stat.baseValueCalculation){
this.statBaseValue = evaluateCalculation(stat.baseValueCalculation, memo); let {value, errors} = evaluateCalculation(stat.baseValueCalculation, memo);
this.base = +this.statBaseValue; this.statBaseValue = value;
if (errors.length){
this.baseValueErrors = errors;
} else {
delete this.baseValueErrors;
}
this.base = this.statBaseValue;
} else { } else {
this.base = 0; this.base = 0;
} }

View File

@@ -20,6 +20,7 @@ export default function computeEffect(effect, memo){
applyToggles(effect, memo); applyToggles(effect, memo);
// Determine result of effect calculation // Determine result of effect calculation
delete effect.errors;
if (!effect.calculation){ if (!effect.calculation){
if(effect.operation === 'add' || effect.operation === 'base'){ if(effect.operation === 'add' || effect.operation === 'base'){
effect.result = 0; effect.result = 0;
@@ -31,7 +32,11 @@ export default function computeEffect(effect, memo){
} else if(_.contains(['advantage', 'disadvantage', 'fail'], effect.operation)){ } else if(_.contains(['advantage', 'disadvantage', 'fail'], effect.operation)){
effect.result = 1; effect.result = 1;
} else { } else {
effect.result = evaluateCalculation(effect.calculation, memo); let {value, errors} = evaluateCalculation(effect.calculation, memo);
effect.result = value;
if (errors.length){
effect.errors = errors;
}
} }
effect.computationDetails.computed = true; effect.computationDetails.computed = true;
effect.computationDetails.busyComputing = false; effect.computationDetails.busyComputing = false;

View File

@@ -25,7 +25,13 @@ export default function computeToggle(toggle, memo){
} else if (Number.isFinite(+toggle.condition)){ } else if (Number.isFinite(+toggle.condition)){
toggle.toggleResult = !!+toggle.condition; toggle.toggleResult = !!+toggle.condition;
} else { } else {
toggle.toggleResult = evaluateCalculation(toggle.condition, memo); let {value, errors} = evaluateCalculation(toggle.condition, memo);
toggle.toggleResult = value;
if (errors.length){
toggle.errors = errors;
} else {
delete toggle.errors;
}
} }
toggle.computationDetails.computed = true; toggle.computationDetails.computed = true;
toggle.computationDetails.busyComputing = false; toggle.computationDetails.busyComputing = false;

View File

@@ -1,15 +1,20 @@
import bareSymbolSubtitutor from '/imports/api/creature/computation/utility/bareSymbolSubtitutor.js';
import computeStat from '/imports/api/creature/computation/computeStat.js'; import computeStat from '/imports/api/creature/computation/computeStat.js';
import math from '/imports/math.js'; import math from '/imports/math.js';
/* Convert a calculation into a constant output and errors*/
export default function evaluateCalculation(string, memo){ export default function evaluateCalculation(string, memo){
if (!string) return string; if (!string) return string;
let errors = [];
// Parse the string using mathjs // Parse the string using mathjs
let calc; let calc;
try { try {
calc = math.parse(string); calc = math.parse(string);
} catch (e) { } catch (e) {
return string; errors.push({
type: 'parsing',
message: e.message || e
});
return {errors, value: string};
} }
// Ensure all symbol nodes are defined and coputed // Ensure all symbol nodes are defined and coputed
calc.traverse(node => { calc.traverse(node => {
@@ -20,12 +25,73 @@ export default function evaluateCalculation(string, memo){
} }
} }
}); });
// Ensure any bare symbols are value accessors instead // Replace all symbols with their subtitution
let substitutedCalc = calc.transform(bareSymbolSubtitutor(memo.statsByVariableName)); let substitutedCalc = calc.transform(
symbolSubtitutor(memo.statsByVariableName, errors)
);
// Evaluate the expression to a number or return with substitutions // Evaluate the expression to a number or return with substitutions
try { try {
return substitutedCalc.evaluate(memo.statsByVariableName); let value = substitutedCalc.evaluate(memo.statsByVariableName);
return {errors, value};
} catch (e){ } catch (e){
return substitutedCalc.toString(); errors.push({
type: 'evaluation',
message: e.message || e
});
let value = substitutedCalc.toString();
return {errors, value};
}
}
// returns a function to replace all symbols with either their resolved value
// or zero, keeping the errors
function symbolSubtitutor(scope, errors){
return function(node){
// mark symbol nodes that are children of function nodes to be skipped
if (node.isFunctionNode){
let fn = node.fn;
if (fn && fn.isSymbolNode){
fn.skipReplacement = true;
}
return node;
} else if (node.isSymbolNode && node.skipReplacement !== true){
//bare symbols of name "stat", should search for stat.value
let stat = scope[node.name];
if (stat){
if (stat.value === undefined){
errors.push({
type: 'subsitution',
message: `${node.name} does not have a value, set to 0`
});
return new math.ConstantNode(0);
} else {
return new math.ConstantNode(stat.value);
}
} else {
try {
return new math.ConstantNode(node.evaluate(scope));
} catch (e) {
errors.push({
type: 'subsitution',
message: `${node.name} not found, set to 0`
});
return new math.ConstantNode(0);
}
}
} else if (node.isAccessorNode){
try {
let value = node.evaluate(scope);
if (value === undefined) throw 'Not found';
return new math.ConstantNode(value);
} catch (e) {
errors.push({
type: 'subsitution',
message: `${node.toString()} not found, set to 0`
});
return new math.ConstantNode(0);
}
} else {
return node;
}
} }
} }

View File

@@ -1,5 +1,5 @@
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
/* /*
* Effects are reason-value attached to skills and abilities * Effects are reason-value attached to skills and abilities
* that modify their final value or presentation in some way * that modify their final value or presentation in some way
@@ -48,7 +48,15 @@ const ComputedOnlyEffectSchema = new SimpleSchema({
type: SimpleSchema.oneOf(Number, String, Boolean), type: SimpleSchema.oneOf(Number, String, Boolean),
optional: true, optional: true,
}, },
}) // The errors encountered while computing the result
errors: {
type: Array,
optional: true,
},
'errors.$':{
type: ErrorSchema,
},
});
const ComputedEffectSchema = new SimpleSchema() const ComputedEffectSchema = new SimpleSchema()
.extend(ComputedOnlyEffectSchema) .extend(ComputedOnlyEffectSchema)

View File

@@ -27,6 +27,14 @@ const ComputedOnlyToggleSchema = new SimpleSchema({
type: SimpleSchema.oneOf(Number, String, Boolean), type: SimpleSchema.oneOf(Number, String, Boolean),
optional: true, optional: true,
}, },
// The errors encountered while computing the result
errors: {
type: Array,
optional: true,
},
'errors.$':{
type: String,
},
}); });
const ComputedToggleSchema = new SimpleSchema() const ComputedToggleSchema = new SimpleSchema()

View File

@@ -0,0 +1,14 @@
import SimpleSchema from 'simpl-schema';
const ErrorSchema = new SimpleSchema({
// The roll that determines how much to change the attribute
message: {
type: String,
},
// Who this adjustment applies to
type: {
type: String,
},
});
export default ErrorSchema;

View File

@@ -6,48 +6,35 @@
:error-messages="errors.name" :error-messages="errors.name"
@change="change('name', ...arguments)" @change="change('name', ...arguments)"
/> />
<div class="layout row wrap justify-start"> <smart-select
<smart-select label="Operation"
label="Operation" append-icon="arrow_drop_down"
append-icon="arrow_drop_down" class="mx-2"
class="mx-2" :error-messages="errors.operation"
:error-messages="errors.operation" :menu-props="{transition: 'slide-y-transition', lazy: true}"
:menu-props="{transition: 'slide-y-transition', lazy: true}" :items="operations"
:items="operations" :value="model.operation"
:value="model.operation" @change="change('operation', ...arguments)"
@change="change('operation', ...arguments)" >
<v-icon
slot="prepend"
class="icon"
:class="iconClass"
>
{{ displayedIcon }}
</v-icon>
<template
slot="item"
slot-scope="item"
> >
<v-icon <v-icon
slot="prepend" class="icon mr-2"
class="icon"
:class="iconClass"
> >
{{ displayedIcon }} {{ getEffectIcon(item.item.value, 1) }}
</v-icon> </v-icon>
<template {{ item.item.text }}
slot="item" </template>
slot-scope="item" </smart-select>
>
<v-icon
class="icon mr-2"
>
{{ getEffectIcon(item.item.value, 1) }}
</v-icon>
{{ item.item.text }}
</template>
</smart-select>
<text-field
label="Value"
class="mr-2"
:persistent-hint="needsValue"
:value="needsValue ? (model.calculation) : ' '"
:disabled="!needsValue"
:error-messages="errors.calculation"
:hint="!isFinite(model.calculation) && model.result ? model.result + '' : '' "
@change="change('calculation', ...arguments)"
/>
</div>
<smart-combobox <smart-combobox
label="Stats" label="Stats"
class="mr-2" class="mr-2"
@@ -59,6 +46,36 @@
:error-messages="errors.stats" :error-messages="errors.stats"
@change="change('stats', ...arguments)" @change="change('stats', ...arguments)"
/> />
<text-field
label="Value"
class="mr-2"
:persistent-hint="needsValue"
:value="needsValue ? (model.calculation) : ' '"
:disabled="!needsValue"
:error-messages="errors.calculation"
:hint="!isFinite(model.calculation) && model.result ? model.result + '' : '' "
@change="change('calculation', ...arguments)"
/>
<div
v-if="model.errors && model.errors.length"
two-line
>
<v-slide-x-transition
group
leave-absolute
>
<v-alert
v-for="error in model.errors"
:key="error.message"
:value="true"
:icon="errorIcon(error.type)"
:color="errorColor(error.type)"
outline
>
{{ error.message }}
</v-alert>
</v-slide-x-transition>
</div>
</div> </div>
</template> </template>
@@ -128,6 +145,24 @@
}, },
}, },
methods: { methods: {
errorIcon(type){
if (type === 'subsitution'){
return 'info';
} else if (type === 'evaluation'){
return 'warning';
} else {
return 'error'
}
},
errorColor(type){
if (type === 'subsitution'){
return 'info';
} else if (type === 'evaluation'){
return 'warning';
} else {
return 'error'
}
},
getEffectIcon, getEffectIcon,
} }
}; };