Allowed attributes to take calculations as their base value

This commit is contained in:
Thaum Rystra
2020-04-28 16:23:52 +02:00
parent ee0fb72d56
commit eae35062d0
10 changed files with 89 additions and 65 deletions

View File

@@ -1,6 +1,13 @@
import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js';
export default class EffectAggregator{
constructor(stat){
this.base = stat.baseValue || 0;
constructor(stat, memo){
if (stat.baseValueCalculation){
this.statBaseValue = evaluateCalculation(stat.baseValueCalculation, memo);
this.base = +this.statBaseValue;
} else {
this.base = 0;
}
this.add = 0;
this.mul = 1;
this.min = Number.NEGATIVE_INFINITY;
@@ -15,47 +22,47 @@ export default class EffectAggregator{
addEffect(effect){
let result = effect.result;
switch(effect.operation){
case "base":
case 'base':
// Take the largest base value
this.base = result > this.base ? result : this.base;
break;
case "add":
case 'add':
// Add all adds together
this.add += result;
break;
case "mul":
case 'mul':
// Multiply the muls together
this.mul *= result;
break;
case "min":
case 'min':
// Take the largest min value
this.min = result > this.min ? result : this.min;
break;
case "max":
case 'max':
// Take the smallest max value
this.max = result < this.max ? result : this.max;
break;
case "advantage":
case 'advantage':
// Sum number of advantages
this.advantage++;
break;
case "disadvantage":
case 'disadvantage':
// Sum number of disadvantages
this.disadvantage++;
break;
case "passiveAdd":
case 'passiveAdd':
// Add all passive adds together
this.passiveAdd += result;
break;
case "fail":
case 'fail':
// Sum number of fails
this.fail++;
break;
case "conditional":
case 'conditional':
// Store array of conditionals
this.conditional.push(result);
break;
case "rollBonus":
case 'rollBonus':
// Store array of roll bonuses
this.rollBonus.push(result);
break;

View File

@@ -1,4 +1,5 @@
import * as math from 'mathjs';
import replaceBareSymbolsWithValueAccessor from '/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js';
export default function evaluateString(string, scope){
let errors = [];
@@ -28,7 +29,7 @@ export default function evaluateString(string, scope){
} catch (e1){
errors.push(e1);
try {
result = simplifyWithAccessors(calc, scope).toHTML();
let result = simplifyWithAccessors(calc, scope).toHTML();
return {result, errors};
} catch (e2){
errors.push(e2);
@@ -37,17 +38,6 @@ export default function evaluateString(string, scope){
}
}
function replaceBareSymbolsWithValueAccessor(node, path, parent) {
if (node.isSymbolNode && path !== 'object') {
const object = new math.SymbolNode(node.name);
const address = new math.ConstantNode('value');
const index = new math.IndexNode([address]);
return new math.AccessorNode(object, index);
} else {
return node;
}
}
function simplifyWithAccessors(calc, scope){
let noAccessorCalc = calc.transform(substituteAccessors(scope));
return math.simplify(noAccessorCalc);
@@ -56,7 +46,7 @@ function simplifyWithAccessors(calc, scope){
// returns a function to replace all accessors with either their resolved value
// or a symbol to simplify with
function substituteAccessors(scope){
return function(node, path, parent){
return function(node){
if (node.isAccessorNode){
try {
return evaluateAccessor(node, scope);
@@ -84,6 +74,7 @@ function replaceAccessorWithSymbol(node){
return symbolNode;
}
/*
function overrideSymbolNodeHTML(symbolNode){
let safeName = escape(symbolNode.name);
symbolNode.toHTML = function(){
@@ -105,3 +96,4 @@ function escape (value) {
return text
}
*/

View File

@@ -1,5 +1,4 @@
import computeStat from '/imports/api/creature/computation/computeStat.js';
import computedValueOfVariableName from '/imports/api/creature/computation/computedValueOfVariableName.js'
export default function combineStat(stat, aggregator, memo){
@@ -18,6 +17,7 @@ function combineAttribute(stat, aggregator){
if (result > aggregator.max) result = aggregator.max;
if (!stat.decimal) result = Math.floor(result);
stat.value = result;
stat.baseValue = aggregator.statBaseValue;
if (stat.attributeType === 'ability') {
stat.modifier = Math.floor((result - 10) / 2);
}
@@ -40,7 +40,9 @@ function combineSkill(stat, aggregator, memo){
if (prof.value > stat.proficiency) stat.proficiency = prof.value;
}
// Get the character's proficiency bonus to apply
let profBonus = computedValueOfVariableName('proficiencyBonus', memo);
let profBonusStat = memo.statsByVariableName['proficiencyBonus'];
let profBonus = profBonusStat && profBonusStat.value;
/** TODO level needs to be on the memo somewhere
if (typeof profBonus !== "number"){
profBonus = Math.floor(char.level / 4 + 1.75);

View File

@@ -17,7 +17,7 @@ export default function computeStat(stat, memo){
return;
}
// Compute and aggregate all the effects
let aggregator = new EffectAggregator(stat)
let aggregator = new EffectAggregator(stat, memo)
each(stat.computationDetails.effects, (effect) => {
computeEffect(effect, memo);
aggregator.addEffect(effect);

View File

@@ -1,14 +0,0 @@
import { get } from 'lodash';
import computeStat from '/imports/api/creature/computation/computeStat.js';
export default function computedValueOfVariableName(name, memo){
let path = name.split('.');
let statName = path[0];
let statPath = path.slice(1);
const stat = get(memo.statsByVariableName, statName);
if (!stat) return null;
if (!stat.computationDetails.computed){
computeStat(stat, memo);
}
return statPath.length ? get(stat, statPath) : stat.value;
}

View File

@@ -1,4 +1,5 @@
import computedValueOfVariableName from '/imports/api/creature/computation/computedValueOfVariableName.js'
import replaceBareSymbolsWithValueAccessor from '/imports/api/creature/computation/utility/replaceBareSymbolsWithValueAccessor.js';
import computeStat from '/imports/api/creature/computation/computeStat.js';
import * as math from 'mathjs';
export default function evaluateCalculation(string, memo){
@@ -11,20 +12,20 @@ export default function evaluateCalculation(string, memo){
console.error(e);
return string;
}
// Replace all symbols with known values
let substitutedCalc = calc.transform(node => {
if (node.isSymbolNode) {
let val = computedValueOfVariableName(node.name, memo);
if (val === null) return node;
return new math.ConstantNode(val);
}
else {
return node;
// Ensure all symbol nodes are defined and coputed
calc.traverse(node => {
if (node.isSymbolNode){
let stat = memo.statsByVariableName[node.name];
if (stat && !stat.computationDetails.computed){
computeStat(stat, memo);
}
}
});
// Ensure any bare symbols are value accessors instead
let substitutedCalc = calc.transform(replaceBareSymbolsWithValueAccessor);
// Evaluate the expression to a number or return with substitutions
try {
return substitutedCalc.evaluate();
return substitutedCalc.evaluate(memo.statsByVariableName);
} catch (e){
return substitutedCalc.toString();
}

View File

@@ -0,0 +1,12 @@
import * as math from 'mathjs';
export default function replaceBareSymbolsWithValueAccessor(node, path) {
if (node.isSymbolNode && path !== 'object') {
const object = new math.SymbolNode(node.name);
const address = new math.ConstantNode('value');
const index = new math.IndexNode([address]);
return new math.AccessorNode(object, index);
} else {
return node;
}
}

View File

@@ -32,9 +32,13 @@ export default function writeAlteredProperties(memo){
if (!isEqual(original[key], changed[key])){
if (!op) op = newOperation(_id, changed.type);
let value = changed[key];
// Use null instead of undefined because it works with the $set operator
if (value === undefined) value = null;
op.updateOne.update.$set[key] = value;
if (value === undefined){
// Unset values that become undefined
addUnsetOp(op, key);
} else {
// Set values that changed to something else
addSetOp(op, key, value);
}
}
}
if (op){
@@ -48,7 +52,7 @@ function newOperation(_id, type){
let newOp = {
updateOne: {
filter: {_id},
update: {'$set': {}},
update: {},
}
};
if (Meteor.isClient){
@@ -57,6 +61,22 @@ function newOperation(_id, type){
return newOp;
}
function addSetOp(op, key, value){
if (op.updateOne.update.$set){
op.updateOne.update.$set[key] = value;
} else {
op.updateOne.update.$set = {[key]: value};
}
}
function addUnsetOp(op, key){
if (op.updateOne.update.$unset){
op.updateOne.update.$unset[key] = 1;
} else {
op.updateOne.update.$unset = {[key]: 1};
}
}
function bulkWriteProperties(bulkWriteOps){
if (!bulkWriteOps.length) return;
if (Meteor.isServer){

View File

@@ -34,8 +34,8 @@ let AttributeSchema = new SimpleSchema({
index: 1,
},
// The starting value, before effects
baseValue: {
type: Number,
baseValueCalculation: {
type: String,
optional: true,
},
// Description of what the attribute is used for
@@ -63,6 +63,11 @@ let AttributeSchema = new SimpleSchema({
});
let ComputedOnlyAttributeSchema = new SimpleSchema({
// The result of baseValueCalculation
baseValue: {
type: SimpleSchema.oneOf(Number, String, Boolean),
optional: true,
},
// The computed value of the attribute
value: {
type: SimpleSchema.oneOf(Number, String, Boolean),

View File

@@ -3,13 +3,12 @@
<div class="layout column align-center">
<text-field
label="Base Value"
type="number"
class="base-value-field text-xs-center large-format no-flex"
:value="model.baseValue"
class="base-value-field"
:value="model.baseValueCalculation"
hint="This is the value of the attribute before effects are applied"
:error-messages="errors.baseValue"
:error-messages="errors.baseValueCalculation"
:debounce-time="debounceTime"
@change="(value, ack) => $emit('change', {path: ['baseValue'], value, ack})"
@change="(value, ack) => $emit('change', {path: ['baseValueCalculation'], value, ack})"
/>
</div>
<div class="layout row wrap">