Made constants work in calculations performed after recomputation
This commit is contained in:
@@ -13,42 +13,43 @@ export default function applyAdjustment({
|
|||||||
...creature.variables,
|
...creature.variables,
|
||||||
...actionContext,
|
...actionContext,
|
||||||
};
|
};
|
||||||
try {
|
var {result, context} = evaluateString({
|
||||||
var {result, errors} = evaluateString(prop.amount, scope, 'reduce');
|
string: prop.amount,
|
||||||
if (typeof result !== 'number') {
|
scope,
|
||||||
log.content.push({
|
fn: 'reduce'
|
||||||
name: 'Attribute damage',
|
});
|
||||||
error: errors.join(', ') || 'Something went wrong',
|
context.errors.forEach(e => {
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage error',
|
||||||
error: e.toString(),
|
error: e.message || e.toString(),
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
if (damageTargets) {
|
if (damageTargets) {
|
||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
if (prop.target === 'each'){
|
if (prop.target === 'each'){
|
||||||
result = evaluateString(prop.amount, scope, 'reduce');
|
({result} = evaluateString({
|
||||||
|
string: prop.amount,
|
||||||
|
scope,
|
||||||
|
fn: 'reduce'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
damagePropertiesByName.call({
|
damagePropertiesByName.call({
|
||||||
creatureId: target._id,
|
creatureId: target._id,
|
||||||
variableName: prop.stat,
|
variableName: prop.stat,
|
||||||
operation: prop.operation || 'increment',
|
operation: prop.operation || 'increment',
|
||||||
value: result
|
value: result.value,
|
||||||
});
|
});
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
||||||
result: `${-result}`,
|
result: `${result.isNumber ? -result.value : result.toString()}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
||||||
result: `${-result}`,
|
result: `${result.isNumber ? -result.value : result.toString()}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,49 +15,79 @@ export default function applyDamage({
|
|||||||
...creature.variables,
|
...creature.variables,
|
||||||
...actionContext,
|
...actionContext,
|
||||||
};
|
};
|
||||||
|
// Add the target's variables to the scope
|
||||||
if (targets.length === 1){
|
if (targets.length === 1){
|
||||||
scope.target = targets[0].variables;
|
scope.target = targets[0].variables;
|
||||||
}
|
}
|
||||||
|
// Determine if the hit is critical
|
||||||
let criticalHit = !!(
|
let criticalHit = !!(
|
||||||
actionContext.criticalHit &&
|
actionContext.criticalHit &&
|
||||||
actionContext.criticalHit.value &&
|
actionContext.criticalHit.value &&
|
||||||
prop.damageType !== 'healing' // Can't critically heal
|
prop.damageType !== 'healing' // Can't critically heal
|
||||||
);
|
);
|
||||||
|
// Double the damage rolls if the hit is critical
|
||||||
let context = new CompilationContext({
|
let context = new CompilationContext({
|
||||||
doubleRolls: criticalHit,
|
doubleRolls: criticalHit,
|
||||||
});
|
});
|
||||||
try {
|
|
||||||
var {result, errors} = evaluateString(prop.amount, scope, 'reduce', context);
|
// Compute the roll the first time, logging any errors
|
||||||
if (typeof result !== 'number') {
|
var {result} = evaluateString({
|
||||||
log.content.push({
|
string: prop.amount,
|
||||||
error: errors.join(', '),
|
scope,
|
||||||
});
|
fn: 'reduce',
|
||||||
}
|
context
|
||||||
} catch (e){
|
});
|
||||||
|
|
||||||
|
// If the result is an error bail out now
|
||||||
|
if (result.constructor.name === 'ErrorNode'){
|
||||||
log.content.push({
|
log.content.push({
|
||||||
error: e.toString(),
|
name: 'Damage error',
|
||||||
|
error: result.toString(),
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoise the damage suffix for the log
|
||||||
let suffix = (criticalHit ? ' critical ' : '') +
|
let suffix = (criticalHit ? ' critical ' : '') +
|
||||||
prop.damageType +
|
prop.damageType +
|
||||||
(prop.damageType !== 'healing' ? ' damage': '');
|
(prop.damageType !== 'healing' ? ' damage': '');
|
||||||
|
|
||||||
if (damageTargets && damageTargets.length) {
|
if (damageTargets && damageTargets.length) {
|
||||||
|
// Iterate through all the targets
|
||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||||
|
|
||||||
|
// Reroll the damage if needed
|
||||||
if (prop.target === 'each'){
|
if (prop.target === 'each'){
|
||||||
result = evaluateString(prop.amount, scope, 'reduce');
|
({result, context} = evaluateString({
|
||||||
|
string: prop.amount,
|
||||||
|
scope,
|
||||||
|
fn: 'reduce'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
// If the result is an error or not a number bail out now
|
||||||
|
if (result.constructor.name === 'ErrorNode' || !result.isNumber){
|
||||||
|
log.content.push({
|
||||||
|
name: 'Damage error',
|
||||||
|
error: result.toString(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal the damage to the target
|
||||||
let damageDealt = dealDamage.call({
|
let damageDealt = dealDamage.call({
|
||||||
creatureId: target._id,
|
creatureId: target._id,
|
||||||
damageType: prop.damageType,
|
damageType: prop.damageType,
|
||||||
amount: result,
|
amount: result.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log the damage done
|
||||||
if (target._id === creature._id){
|
if (target._id === creature._id){
|
||||||
|
// Target is same as self, log damage as such
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name,
|
name,
|
||||||
result: damageDealt,
|
result: damageDealt,
|
||||||
details: suffix + 'to self',
|
details: suffix + ' to self',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.content.push({
|
log.content.push({
|
||||||
@@ -66,6 +96,7 @@ export default function applyDamage({
|
|||||||
result: damageDealt,
|
result: damageDealt,
|
||||||
details: suffix + `${target.name && ' to '}${target.name}`,
|
details: suffix + `${target.name && ' to '}${target.name}`,
|
||||||
});
|
});
|
||||||
|
// Log the damage received on that creature's log as well
|
||||||
insertCreatureLog.call({
|
insertCreatureLog.call({
|
||||||
log: {
|
log: {
|
||||||
content: [{
|
content: [{
|
||||||
@@ -80,9 +111,10 @@ export default function applyDamage({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// There are no targets, just log the result
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
||||||
result,
|
result: result.toString(),
|
||||||
details: suffix,
|
details: suffix,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,23 +10,17 @@ export default function applyRoll({
|
|||||||
...creature.variables,
|
...creature.variables,
|
||||||
...actionContext,
|
...actionContext,
|
||||||
};
|
};
|
||||||
try {
|
var {result} = evaluateString({
|
||||||
var {result, errors} = evaluateString(prop.roll, scope, 'reduce');
|
string: prop.roll,
|
||||||
actionContext[prop.variableName] = result;
|
scope,
|
||||||
log.content.push({
|
fn: 'reduce'
|
||||||
name: prop.name,
|
});
|
||||||
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
|
if (result.isNumber){
|
||||||
result,
|
actionContext[prop.variableName] = result.value;
|
||||||
});
|
|
||||||
if (errors.length) {
|
|
||||||
log.content.push({
|
|
||||||
error: errors.join(', '),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
|
||||||
error: e.toString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: prop.name,
|
||||||
|
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
|
||||||
|
result: result.toString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,17 @@ export default function applySave({
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
// Calculate the DC
|
// Calculate the DC
|
||||||
var {result, errors} = evaluateString(prop.dc, scope, 'reduce');
|
var {result} = evaluateString({
|
||||||
let dc = result;
|
string: prop.dc,
|
||||||
|
scope,
|
||||||
|
fn: 'reduce'
|
||||||
|
});
|
||||||
|
let dc = result.value;
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
resultPrefix: ' DC ',
|
resultPrefix: ' DC ',
|
||||||
result,
|
result: result.toString(),
|
||||||
});
|
});
|
||||||
if (errors.length) {
|
|
||||||
log.content.push({
|
|
||||||
error: errors.join(', '),
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prop.target === 'self'){
|
if (prop.target === 'self'){
|
||||||
let save = CreaturesProperties.findOne({
|
let save = CreaturesProperties.findOne({
|
||||||
'ancestors.id': creature._id,
|
'ancestors.id': creature._id,
|
||||||
|
|||||||
@@ -13,23 +13,22 @@ export default function applyToggle({
|
|||||||
if (Number.isFinite(+prop.condition)){
|
if (Number.isFinite(+prop.condition)){
|
||||||
return !!+prop.condition;
|
return !!+prop.condition;
|
||||||
}
|
}
|
||||||
try {
|
var {result} = evaluateString({
|
||||||
var {result, errors} = evaluateString(prop.condition, scope, 'reduce');
|
string: prop.condition,
|
||||||
if (typeof result !== 'number' && typeof result !== 'boolean') {
|
scope,
|
||||||
log.content.push({
|
fn: 'reduce'
|
||||||
error: errors.join(', '),
|
});
|
||||||
});
|
if (result.constructor.name === 'ErrorNode') {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.name,
|
name: 'Toggle error',
|
||||||
resultPrefix: prop.condition + ' = ',
|
error: result.toString(),
|
||||||
result,
|
|
||||||
});
|
|
||||||
return !!result;
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
|
||||||
error: e.toString(),
|
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: prop.name || 'Toggle',
|
||||||
|
resultPrefix: prop.condition + ' = ',
|
||||||
|
result: result.toString(),
|
||||||
|
});
|
||||||
|
return !!result.value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,67 @@
|
|||||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
||||||
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
||||||
|
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||||
|
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||||
|
|
||||||
//TODO replace constants with their parsed node
|
//TODO replace constants with their parsed node
|
||||||
|
|
||||||
export default function evaluateString(string, scope, fn = 'compile', context){
|
export default function evaluateString({string, scope, fn = 'compile', context}){
|
||||||
let errors = [];
|
if (!context){
|
||||||
|
context = new CompilationContext({});
|
||||||
|
}
|
||||||
if (!string){
|
if (!string){
|
||||||
errors.push('No string provided');
|
context.storeError('No string provided');
|
||||||
return {result: string, errors};
|
return {result: {value: string}, context};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scope) errors.push('No scope provided');
|
if (!scope) context.storeError('No scope provided');
|
||||||
|
|
||||||
// Parse the string using mathjs
|
// Parse the string using mathjs
|
||||||
let node;
|
let node;
|
||||||
try {
|
try {
|
||||||
node = parse(string);
|
node = parse(string);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.push(e);
|
context.storeError(e);
|
||||||
return {result: string, errors};
|
return {result: {value: string}, context};
|
||||||
}
|
|
||||||
if (!context){
|
|
||||||
context = new CompilationContext({});
|
|
||||||
}
|
}
|
||||||
|
node = replaceConstants({calc: node, context, scope});
|
||||||
let result = node[fn](scope, context);
|
let result = node[fn](scope, context);
|
||||||
if (result instanceof ConstantNode){
|
return {result, context};
|
||||||
return {result: result.value, errors: context.errors}
|
}
|
||||||
} else {
|
|
||||||
return {result: result.toString(), errors: context.errors};
|
// Replace constants in the calc with the right ParseNodes
|
||||||
}
|
function replaceConstants({calc, context, scope}){
|
||||||
|
let constFailed = [];
|
||||||
|
calc = calc.replaceNodes(node => {
|
||||||
|
if (!(node instanceof SymbolNode)) return;
|
||||||
|
let constant = scope[node.name];
|
||||||
|
// replace constants that aren't overridden by stats or disabled by a toggle
|
||||||
|
if (constant && constant.type === 'constant'){
|
||||||
|
// Fail if the constant has errors
|
||||||
|
if (constant.errors && constant.errors.length){
|
||||||
|
constFailed.push(node.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let parsedConstantNode;
|
||||||
|
try {
|
||||||
|
parsedConstantNode = parse(constant.calculation);
|
||||||
|
} catch(e){
|
||||||
|
constFailed.push(node.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!parsedConstantNode) constFailed.push(node.name);
|
||||||
|
return parsedConstantNode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
constFailed.forEach(name => {
|
||||||
|
context.storeError({
|
||||||
|
type: 'error',
|
||||||
|
message: `${name} is a constant property with parsing errors`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let failed = !!constFailed.length;
|
||||||
|
if (failed){
|
||||||
|
calc = new ErrorNode({error: 'Failed to replace constants'});
|
||||||
|
}
|
||||||
|
return calc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,28 +4,30 @@ import VERSION from '/imports/constants/VERSION.js';
|
|||||||
|
|
||||||
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
|
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
|
||||||
const fields = [
|
const fields = [
|
||||||
'name',
|
|
||||||
'attributeType',
|
|
||||||
'baseValue',
|
|
||||||
'spellSlotLevelValue',
|
|
||||||
'damage',
|
|
||||||
'decimal',
|
|
||||||
'reset',
|
|
||||||
'resetMultiplier',
|
|
||||||
'value',
|
|
||||||
'currentValue',
|
|
||||||
'modifier',
|
|
||||||
'ability',
|
'ability',
|
||||||
'skillType',
|
|
||||||
'baseProficiency',
|
|
||||||
'abilityMod',
|
'abilityMod',
|
||||||
'advantage',
|
'advantage',
|
||||||
'passiveBonus',
|
'attributeType',
|
||||||
'proficiency',
|
'baseProficiency',
|
||||||
|
'baseValue',
|
||||||
|
'calculation',
|
||||||
'conditionalBenefits',
|
'conditionalBenefits',
|
||||||
'rollBonuses',
|
'currentValue',
|
||||||
|
'damage',
|
||||||
|
'decimal',
|
||||||
'fail',
|
'fail',
|
||||||
'level',
|
'level',
|
||||||
|
'modifier',
|
||||||
|
'name',
|
||||||
|
'passiveBonus',
|
||||||
|
'proficiency',
|
||||||
|
'reset',
|
||||||
|
'resetMultiplier',
|
||||||
|
'rollBonuses',
|
||||||
|
'skillType',
|
||||||
|
'spellSlotLevelValue',
|
||||||
|
'type',
|
||||||
|
'value',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (fullRecompute){
|
if (fullRecompute){
|
||||||
@@ -34,6 +36,12 @@ export default function writeCreatureVariables(memo, creatureId, fullRecompute =
|
|||||||
let condensedStat = pick(stat, fields);
|
let condensedStat = pick(stat, fields);
|
||||||
memo.creatureVariables[variableName] = condensedStat;
|
memo.creatureVariables[variableName] = condensedStat;
|
||||||
});
|
});
|
||||||
|
forOwn(memo.constantsByVariableName, (stat, variableName) => {
|
||||||
|
let condensedStat = pick(stat, fields);
|
||||||
|
if (!memo.creatureVariables[variableName]){
|
||||||
|
memo.creatureVariables[variableName] = condensedStat;
|
||||||
|
}
|
||||||
|
});
|
||||||
Creatures.update(creatureId, {$set: {
|
Creatures.update(creatureId, {$set: {
|
||||||
variables: memo.creatureVariables,
|
variables: memo.creatureVariables,
|
||||||
computeVersion: VERSION,
|
computeVersion: VERSION,
|
||||||
|
|||||||
@@ -143,10 +143,10 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
|
||||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
|
||||||
|
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -243,15 +243,12 @@ export default {
|
|||||||
// the quantity to fill
|
// the quantity to fill
|
||||||
nodes = nodes.filter(node => {
|
nodes = nodes.filter(node => {
|
||||||
if (node.slotFillerCondition){
|
if (node.slotFillerCondition){
|
||||||
let context = new CompilationContext();
|
let {result} = evaluateString({
|
||||||
let conditionResult;
|
string: node.slotFillerCondition,
|
||||||
try {
|
scope: this.creature.variables,
|
||||||
conditionResult = parse(node.slotFillerCondition)
|
fn: 'reduce',
|
||||||
.reduce(this.creature.variables, context);
|
});
|
||||||
} catch (e){
|
if (!result.value) return false;
|
||||||
console.warn(e);
|
|
||||||
}
|
|
||||||
if (conditionResult && !conditionResult.value) return false;
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
node.type === 'slotFiller' &&
|
node.type === 'slotFiller' &&
|
||||||
|
|||||||
Reference in New Issue
Block a user