Allowed attributes to take calculations as their base value
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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){
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user