From 0cdec4a429284a235fa3a874f15f6604087979de Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sat, 9 Oct 2021 12:36:06 +0200 Subject: [PATCH] Start of action system re-write --- .../methods/damagePropertiesByName.js | 6 +- .../methods/damageProperty.js | 27 ++- app/imports/api/creature/log/CreatureLogs.js | 2 +- .../api/engine/actions/applyProperty.js | 15 ++ .../applyPropertyByType/applyAction.js | 200 ++++++++++++++++++ .../applyPropertyByType/applyAdjustment.js | 61 ++++++ .../applyPropertyByType/applyBranch.js | 42 ++++ .../actions/applyPropertyByType/applyBuff.js | 95 +++++++++ .../applyPropertyByType/shared/logErrors.js | 7 + .../shared/recalculateCalculation.js | 9 + .../shared/recalculateInlineCalculations.js | 13 ++ app/imports/api/engine/actions/doAction.js | 103 +++++++++ .../computeByType/computeAction.js | 1 - .../computeComputation/computeCalculations.js | 33 +-- .../utility/embedInlineCalculations.js | 12 ++ .../utility/evaluateCalculation.js | 19 ++ .../writeComputation/writeScope.js | 5 + app/imports/api/engine/computeCreature.js | 2 + .../api/engine/oldActions/applyAction.js | 13 -- .../api/engine/oldActions/applyAttack.js | 4 +- .../api/engine/oldActions/applyProperties.js | 25 ++- app/imports/api/properties/Actions.js | 6 - app/imports/api/properties/Adjustments.js | 7 +- app/imports/api/properties/Branch.js | 49 +++++ app/imports/api/properties/Buffs.js | 5 +- app/imports/api/properties/Damages.js | 5 +- app/imports/api/properties/SavingThrows.js | 5 +- .../migrations/server/2.0-beta.33-dbv1.js | 12 ++ app/imports/parser/parseTree/array.js | 9 +- app/imports/parser/parseTree/call.js | 9 +- app/imports/parser/parseTree/if.js | 11 +- app/imports/parser/parseTree/index.js | 10 +- app/imports/parser/parseTree/not.js | 11 +- app/imports/parser/parseTree/operator.js | 10 +- app/imports/parser/parseTree/parenthesis.js | 11 +- app/imports/parser/parseTree/roll.js | 16 +- app/imports/parser/parseTree/rollArray.js | 3 - app/imports/parser/parseTree/unaryOperator.js | 9 +- app/imports/parser/resolve.js | 17 +- app/imports/parser/{roll.js => rollDice.js} | 2 +- app/imports/ui/properties/forms/SkillForm.vue | 1 + 41 files changed, 783 insertions(+), 119 deletions(-) create mode 100644 app/imports/api/engine/actions/applyProperty.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyAction.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyBranch.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyBuff.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/shared/logErrors.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/shared/recalculateInlineCalculations.js create mode 100644 app/imports/api/engine/actions/doAction.js create mode 100644 app/imports/api/engine/computation/utility/embedInlineCalculations.js create mode 100644 app/imports/api/engine/computation/utility/evaluateCalculation.js create mode 100644 app/imports/api/engine/computation/writeComputation/writeScope.js delete mode 100644 app/imports/api/engine/oldActions/applyAction.js create mode 100644 app/imports/api/properties/Branch.js rename app/imports/parser/{roll.js => rollDice.js} (79%) diff --git a/app/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js b/app/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js index 673f71ad..6428eeb3 100644 --- a/app/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js +++ b/app/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js @@ -5,7 +5,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur import Creatures from '/imports/api/creature/creatures/Creatures.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; -import { computeCreatureDependencyGroup } from '/imports/api/engine/computeCreature.js'; const damagePropertiesByName = new ValidatedMethod({ name: 'CreatureProperties.damagePropertiesByName', @@ -29,14 +28,13 @@ const damagePropertiesByName = new ValidatedMethod({ // Check permissions let creature = Creatures.findOne(creatureId, { fields: { - damageMultipliers: 1, + variables: 1, owner: 1, readers: 1, writers: 1, }, }); assertEditPermission(creature, this.userId); - let lastProperty; CreatureProperties.find({ 'ancestors.id': creatureId, variableName, @@ -48,9 +46,7 @@ const damagePropertiesByName = new ValidatedMethod({ if (!schema.allowsKey('damage')) return; // Damage the property damagePropertyWork({property, operation, value}); - lastProperty = property; }); - if (lastProperty) computeCreatureDependencyGroup(lastProperty); } }); diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index d00ea3c4..2aa1c554 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -45,21 +45,17 @@ const damageProperty = new ValidatedMethod({ }); export function damagePropertyWork({property, operation, value}){ + let damage, newValue; if (operation === 'set'){ - let currentValue = property.value; + const currentValue = property.value; // Set represents what we want the value to be after damage // So we need the actual damage to get to that value - let damage = currentValue - value; + damage = currentValue - value; // Damage can't exceed total value if (damage > currentValue) damage = currentValue; // Damage must be positive if (damage < 0) damage = 0; - CreatureProperties.update(property._id, { - $set: {damage} - }, { - selector: property - }); - return currentValue - damage; + newValue = property.total - damage; } else if (operation === 'increment'){ let currentValue = property.value - (property.damage || 0); let currentDamage = property.damage; @@ -68,13 +64,16 @@ export function damagePropertyWork({property, operation, value}){ if (increment > currentValue) increment = currentValue; // Can't decrease damage below zero if (-increment > currentDamage) increment = -currentDamage; - CreatureProperties.update(property._id, { - $inc: {damage: increment} - }, { - selector: property - }); - return increment; + damage = currentDamage + increment; + newValue = property.total - damage; } + + // Write the results + CreatureProperties.update(property._id, { + $set: {damage, value: newValue} + }, { + selector: property + }); } export default damageProperty; diff --git a/app/imports/api/creature/log/CreatureLogs.js b/app/imports/api/creature/log/CreatureLogs.js index 5b10a938..c464a71d 100644 --- a/app/imports/api/creature/log/CreatureLogs.js +++ b/app/imports/api/creature/log/CreatureLogs.js @@ -125,7 +125,7 @@ export function insertCreatureLogWork({log, creature, method}){ // Insert it let id = CreatureLogs.insert(log); if (Meteor.isServer){ - method.unblock(); + method?.unblock(); removeOldLogs(creature._id); logWebhook({log, creature}); } diff --git a/app/imports/api/engine/actions/applyProperty.js b/app/imports/api/engine/actions/applyProperty.js new file mode 100644 index 00000000..24652a73 --- /dev/null +++ b/app/imports/api/engine/actions/applyProperty.js @@ -0,0 +1,15 @@ + +const applyPropertyByType = { + action, + branch, + buff, + damage, + roll, + savingThrow, + spell, + toggle, +}; + +export default function applyProperty(node, ...args){ + return applyPropertyByType[node.node.type]?.(node, ...args); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js new file mode 100644 index 00000000..b2507456 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -0,0 +1,200 @@ +import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; +import rollDice from '/imports/parser/rollDice.js'; +import applyProperty from '../applyProperty.js'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js'; +import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; + +export default function applyAction(node, {creature, targets, scope, log}){ + const prop = node.node; + if (prop.target === 'self') targets = [creature]; + const failed = spendResources({prop, log, scope}); + if (failed) return; + + let content = { name: prop.name }; + if (prop.summary?.text){ + recalculateInlineCalculations(prop.summary, scope, log); + content.value = prop.summary.value; + } + log.content.push(content); + if (prop.attackRoll && prop.attackRoll.calculation){ + if (targets.length){ + targets.forEach(target => { + applyAttackToTarget({prop, target, scope, log}); + // Apply the children, but only to the current target + applyChildren(node, {targets: [target], scope, log}); + }); + } else { + applyAttackWithoutTarget({prop, scope, log}); + applyChildren(node, {creature, targets, scope, log}); + } + } +} + +function applyAttackWithoutTarget({prop, scope, log}){ + delete scope['$attackHit']; + delete scope['$attackMiss']; + + recalculateCalculation(prop.rollBonus, scope, log); + + let value = rollDice(1, 20)[0]; + scope['$attackRoll'] = {value}; + let criticalHitTarget = scope.criticalHitTarget?.value || 20; + let criticalHit = value >= criticalHitTarget; + if (criticalHit) scope['$criticalHit'] = {value: true}; + let result = value + prop.rollBonus.value; + scope['$toHit'] = {value: result}; + log.content.push({ + name: criticalHit ? 'Critical Hit!' : 'To Hit', + value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result, + }); +} + +function applyAttackToTarget({prop, target, scope, log}){ + delete scope['$attackHit']; + delete scope['$attackMiss']; + + recalculateCalculation(prop.rollBonus, scope, log); + + const value = rollDice(1, 20)[0]; + scope['$attackRoll'] = {value}; + const criticalHitTarget = scope.criticalHitTarget?.value || 20; + const criticalHit = value >= criticalHitTarget; + const criticalMiss = value === 1; + if (criticalHit) scope['$criticalHit'] = {value: true}; + if (criticalMiss) scope['$criticalMiss'] = {value: true}; + const result = value + prop.rollBonus.value; + scope['$toHit'] = {value: result}; + if (target.variables.armor){ + const armor = target.variables.armor.value; + const name = criticalHit ? 'Critical Hit!' : + criticalMiss ? 'Critical miss!' : + result > armor ? 'Hit!' : + 'Miss!' + log.content.push({ + name, + value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result, + }); + if ((result > armor) || (criticalHit)){ + scope['$attackHit'] = true; + } else { + scope['$attackMiss'] = true; + } + } else { + log.content.push({ + name: 'Error', + value:'Target has no `armor`', + }); + log.content.push({ + name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit', + value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result, + }); + } +} + +function applyChildren(node, args){ + node.children.forEach(child => applyProperty(child, args)); +} + +function spendResources({prop, log, scope}){ + // Check Uses + if (prop.usesUsed >= prop.uses?.value){ + log.content.push({ + name: 'Error', + value: `${prop.name || 'action'} does not have enough uses left`, + }); + return true; + } + // Resources + if (prop.insufficientResources){ + log.content.push({ + name: 'Error', + value: 'This creature doesn\'t have sufficient resources to perform this action', + }); + return true; + } + // Items + let itemQuantityAdjustments = []; + let spendLog = []; + let gainLog = []; + try { + prop.resources.itemsConsumed.forEach(itemConsumed => { + if (!itemConsumed.itemId){ + throw 'No ammo was selected for this prop'; + } + let item = CreatureProperties.findOne(itemConsumed.itemId); + if (!item || item.ancestors[0].id !== prop.ancestors[0].id){ + throw 'The prop\'s ammo was not found on the creature'; + } + if (!item.equipped){ + throw 'The selected ammo is not equipped'; + } + if (!itemConsumed.quantity) return; + itemQuantityAdjustments.push({ + property: item, + operation: 'increment', + value: itemConsumed.quantity, + }); + let logName = item.name; + if (itemConsumed.quantity > 1 || itemConsumed.quantity < -1){ + logName = item.plural || logName; + } + if (itemConsumed.quantity > 0){ + spendLog.push(logName + ': ' + itemConsumed.quantity); + } else if (itemConsumed.quantity < 0){ + gainLog.push(logName + ': ' + -itemConsumed.quantity); + } + }); + } catch (e){ + log.content.push({ + name: 'Error', + value: e, + }); + return true; + } + // No more errors should be thrown after this line + // Now that we have confirmed that there are no errors, do actual work + //Items + itemQuantityAdjustments.forEach(adjustQuantityWork); + + // Use uses + if (prop.usesResult){ + CreatureProperties.update(prop._id, { + $inc: {usesUsed: 1} + }, { + selector: prop + }); + log.content.push({ + name: 'Uses left', + value: prop.usesResult - (prop.usesUsed || 0) - 1, + }); + } + + // Damage stats + prop.resources.attributesConsumed.forEach(attConsumed => { + if (!attConsumed.quantity) return; + let stat = scope[attConsumed.variableName]; + if (!stat) return; + damagePropertyWork({ + property: stat, + operation: 'increment', + value: attConsumed.quantity, + }); + if (attConsumed.quantity > 0){ + spendLog.push(stat.name + ': ' + attConsumed.quantity); + } else if (attConsumed.quantity < 0){ + gainLog.push(stat.name + ': ' + -attConsumed.quantity); + } + }); + + // Log all the spending + if (gainLog.length) log.content.push({ + name: 'Gained', + value: gainLog.join('\n'), + }); + if (spendLog.length) log.content.push({ + name: 'Spent', + value: spendLog.join('\n'), + }); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js new file mode 100644 index 00000000..1f2880ec --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js @@ -0,0 +1,61 @@ +import applyProperty from '../applyProperty.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; +import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; + +export default function applyAdjustment(node, { + creature, targets, scope, log +}){ + const prop = node.node; + const damageTargets = prop.target === 'self' ? [creature] : targets; + + if (!prop.amount) { + return applyChildren(node, {creature, targets, scope, log}); + } + + // Evaluate the amount + recalculateCalculation(prop.amount, scope, log); + prop.amount.errors?.forEach(error => { + if (error.type !== 'info'){ + log.content.push({name: 'Error', value: error.message}); + } + }); + const value = prop.amount.value; + if (!isFinite(value)) { + return applyChildren(node, {creature, targets, scope, log}); + } + + if (damageTargets?.length) { + damageTargets.forEach(target => { + let stat = target.variables[prop.stat]; + if (!stat) { + log({ + name: 'Error', + value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set` + }); + return; + } + damagePropertyWork({ + property: stat, + operation: prop.operation, + value, + }); + log.content.push({ + name: 'Attribute damage', + value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + + ` ${value}`, + }); + }); + } else { + log.content.push({ + name: 'Attribute damage', + value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + + ` ${value}`, + }); + } + + return applyChildren(node, {creature, targets, scope, log}); +} + +function applyChildren(node, args){ + node.children.forEach(child => applyProperty(child, args)); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js new file mode 100644 index 00000000..925e131c --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js @@ -0,0 +1,42 @@ +import applyProperty from '../applyProperty.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; + +export default function applyBranch(node, { + creature, targets, scope, log +}){ + const applyChildren = function(){ + node.children.forEach(child => applyProperty(child, { + creature, targets, scope, log + })); + }; + const prop = node.node; + switch(prop.branchType){ + case 'if': + recalculateCalculation(prop.condition, scope, log); + if (prop.condition?.value) applyChildren(); + break; + case 'hit': + if (scope['$attackHit']?.value) applyChildren(); + break; + case 'miss': + if (scope['$attackMiss']?.value) applyChildren(); + break; + case 'failedSave': + if (scope['$saveFailed']?.value) applyChildren(); + break; + case 'successfulSave': + if (scope['$saveSucceeded']?.value) applyChildren(); + break; + case 'eachTarget': + if (targets.length){ + targets.forEach(target => { + node.children.forEach(child => applyProperty(child, { + creature, targets: [target], scope, log + })); + }); + } else { + applyChildren(); + } + break; + } +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js new file mode 100644 index 00000000..405d48c8 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js @@ -0,0 +1,95 @@ +import { + setLineageOfDocs, + renewDocIds +} from '/imports/api/parenting/parenting.js'; +import {setDocToLastOrder} from '/imports/api/parenting/order.js'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js'; +import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js'; +import { get } from 'lodash'; +import resolve, { map } from '/imports/parser/resolve.js'; +import logErrors from './shared/logErrors.js'; + +export default function applyBuff(node, {creature, targets, scope, log}){ + const prop = node.node; + let buffTargets = prop.target === 'self' ? [creature] : targets; + + // Then copy the decendants of the buff to the targets + prop.applied = true; + let propList = [prop]; + function addChildrenToPropList(children){ + children.forEach(child => { + propList.push(child.node); + addChildrenToPropList(child.children); + }); + } + addChildrenToPropList(node.children); + crystalizeVariables({propList, scope, log}); + + let oldParent = { + id: prop.parent.id, + collection: prop.parent.collection, + }; + buffTargets.forEach(target => { + copyNodeListToTarget(propList, target, oldParent); + }); + + // Don't apply the children of the buff, they get copied to the target instead +} + +function copyNodeListToTarget(propList, target, oldParent){ + let ancestry = [{collection: 'creatures', id: target._id}]; + setLineageOfDocs({ + docArray: propList, + newAncestry: ancestry, + oldParent, + }); + renewDocIds({ + docArray: propList, + }); + setDocToLastOrder({ + collection: CreatureProperties, + doc: propList[0], + }); + CreatureProperties.batchInsert(propList); +} + +/** + * Replaces all variables with their resolved values + * except variables of the form `$target.thing.total` become `thing.total` + */ +function crystalizeVariables({propList, scope, log}){ + propList.forEach(prop => { + computedSchemas[prop.type].computedFields().forEach( calcKey => { + applyFnToKey(prop, calcKey, (prop, key) => { + const calcObj = get(prop, key); + if (!calcObj?.parseNode) return; + map(calcObj.parseNode, node => { + // Skip nodes that aren't symbols or accessors + if ( + node.parseType !== 'accessor' && node.parseType !== 'symbol' + ) return node; + // Handle variables + if (node.name === '$target'){ + // strip $target + if (node.parseType === 'accessor'){ + node.name = node.path.shift(); + } else { + // Can't strip symbols + log.content.push({ + name: 'Error', + value: 'Variable `$target` should not be used without a property: $target.property' + }); + } + return node; + } else { + // Resolve all other variables + const {result, context} = resolve('reduce', node, scope); + logErrors(context.errors, log); + return result; + } + }); + }); + }); + }); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/shared/logErrors.js b/app/imports/api/engine/actions/applyPropertyByType/shared/logErrors.js new file mode 100644 index 00000000..219fddd5 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/shared/logErrors.js @@ -0,0 +1,7 @@ +export default function logErrors(errors, log){ + errors?.forEach(error => { + if (error.type !== 'info'){ + log.content.push({name: 'Error', value: error.message}); + } + }); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js new file mode 100644 index 00000000..cdc541fe --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js @@ -0,0 +1,9 @@ +import evaluateCalculation from '../utility/evaluateCalculation.js'; +import logErrors from './logErrors.js'; + +export default function recalculateCalculation(calc, scope, log){ + if (!calc.parseNode) return; + calc._parseLevel = 'reduce'; + evaluateCalculation(calc, scope); + logErrors(calc.errors, log); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateInlineCalculations.js b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateInlineCalculations.js new file mode 100644 index 00000000..3e25072f --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateInlineCalculations.js @@ -0,0 +1,13 @@ +import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations.js'; +import recalculateCalculation from './recalculateCalculation.js' + +export default function recalculateInlineCalculations(inlineCalcObj, scope, log){ + // Skip if there are no calculations + if (!inlineCalcObj?.calculations?.length) return; + // Recalculate each calculation with the current scope + inlineCalcObj.inlineCalculations.forEach(calc => { + recalculateCalculation(calc, scope, log); + }); + // Embed the new calculated values + embedInlineCalculations(inlineCalcObj); +} diff --git a/app/imports/api/engine/actions/doAction.js b/app/imports/api/engine/actions/doAction.js new file mode 100644 index 00000000..8f0bab5e --- /dev/null +++ b/app/imports/api/engine/actions/doAction.js @@ -0,0 +1,103 @@ +import Creatures from '/imports/api/creature/creatures/Creatures.js'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js'; +import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'; +import applyProperty from './applyProperty.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; + +export default function doAction({actionId, targetIds, method}){ + // get the docs + const { + creature, targets, properties, ancestors + } = fetchActionDocs(actionId, targetIds); + const ancestorScope = getAncestorScope(ancestors); + const propertyForest = nodeArrayToTree(properties); + if (propertyForest.length !== 1){ + throw new Meteor.Error(`The action has ${propertyForest.length} top level properties, expected 1`); + } + + // Create the log + let log = CreatureLogSchema.clean({ + creatureId: creature._id, + creatureName: creature.name, + }); + + // Apply the top level property, it is responsible for applying its children + // recursively + const scope = { + ...creature.variables, + ...ancestorScope, + } + applyProperty(propertyForest[0], { + creature, + targets, + scope, + log, + }); + + // Insert the log + insertCreatureLogWork({log, creature, method}); + + // Recompute the creature and targets + Meteor.defer(() => computeCreature(creature._id)); + targetIds.forEach(targetId => { + Meteor.defer(() => computeCreature(targetId)); + }); +} + +function fetchActionDocs(actionId, targetIds){ + // Fetch the action with ancestors only + const action = CreatureProperties.findOne({ + _id: actionId, + removed: {$ne: true}, + }, { + fields: {ancestors: 1} + }); + if (!action) throw new Meteor.Error('The specified action was not found'); + + // Fetch all the action's ancestor creatureProperties + const ancestorIds = []; + action.ancestors.forEach(ref => { + if (ref.collection === 'creatureProperties') { + ancestorIds.push(ref.id); + } + }); + // Get cursor of ancestors + const ancestors = CreatureProperties.find({ + _id: {$in: ancestorIds}, + }, { + sort: {order: 1}, + }); + + // Fetch the action's top level ancestor creature + const creature = Creatures.findOne(action.ancestors[0].id, { + fields: {variables: 1}, + }); + if (!creature) throw new Meteor.Error('The creature for this action was not found'); + + // Fetch all the target creatures + const targets = Creatures.find({ + _id: targetIds, + }, { + fields: {variables: 1}, + }).fetch(); + + // Get cursor of the properties + const properties = CreatureProperties.find({ + $or: [{_id: actionId}, {'ancestors.id': actionId}], + removed: {$ne: true}, + }, { + sort: {order: 1}, + }); + + return {action, creature, targets, properties, ancestors} +} + +// Assumes ancestors are in tree order already +function getAncestorScope(ancestors){ + let scope = {}; + ancestors.forEach(prop => { + scope[`#${prop.type}`] = prop; + }); + return scope; +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index 9b318fa5..df042f9c 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -27,7 +27,6 @@ function computeResources(computation, node){ const att = computation.scope[attConsumed.variableName]; if (!att._id) return; attConsumed.available = att.value; - attConsumed.statId = att._id; attConsumed.statName = att.name; }); } diff --git a/app/imports/api/engine/computation/computeComputation/computeCalculations.js b/app/imports/api/engine/computation/computeComputation/computeCalculations.js index 6b74b5e3..2346324d 100644 --- a/app/imports/api/engine/computation/computeComputation/computeCalculations.js +++ b/app/imports/api/engine/computation/computeComputation/computeCalculations.js @@ -1,5 +1,5 @@ -import resolve, { toString } from '/imports/parser/resolve.js'; -import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; +import embedInlineCalculations from '../utility/embedInlineCalculations.js'; +import evaluateCalculation from '../utility/evaluateCalculation.js'; export default function computeCalculations(computation, node){ if (!node.data) return; @@ -11,32 +11,3 @@ export default function computeCalculations(computation, node){ embedInlineCalculations(inlineCalcObj); }); } - -function evaluateCalculation(calculation, scope){ - const parseNode = calculation.parseNode; - const fn = calculation._parseLevel; - const calculationScope = {...calculation._localScope, ...scope}; - const {result: resultNode, context} = resolve(fn, parseNode, calculationScope); - calculation.errors = context.errors; - if (resultNode?.parseType === 'constant'){ - calculation.value = resultNode.value; - } else if (resultNode?.parseType === 'error'){ - calculation.value = null; - } else { - calculation.value = toString(resultNode); - } - // remove the working fields - delete calculation._parseLevel; - delete calculation._localScope; -} - -function embedInlineCalculations(inlineCalcObj){ - const string = inlineCalcObj.text; - const calculations = inlineCalcObj.inlineCalculations; - if (!string || !calculations) return; - let index = 0; - inlineCalcObj.value = string.replace(INLINE_CALCULATION_REGEX, substring => { - let calc = calculations[index++]; - return (calc && 'value' in calc) ? calc.value : substring; - }); -} diff --git a/app/imports/api/engine/computation/utility/embedInlineCalculations.js b/app/imports/api/engine/computation/utility/embedInlineCalculations.js new file mode 100644 index 00000000..096cf921 --- /dev/null +++ b/app/imports/api/engine/computation/utility/embedInlineCalculations.js @@ -0,0 +1,12 @@ +import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; + +export default function embedInlineCalculations(inlineCalcObj){ + const string = inlineCalcObj.text; + const calculations = inlineCalcObj.inlineCalculations; + if (!string || !calculations) return; + let index = 0; + inlineCalcObj.value = string.replace(INLINE_CALCULATION_REGEX, substring => { + let calc = calculations[index++]; + return (calc && 'value' in calc) ? calc.value : substring; + }); +} diff --git a/app/imports/api/engine/computation/utility/evaluateCalculation.js b/app/imports/api/engine/computation/utility/evaluateCalculation.js new file mode 100644 index 00000000..d88f424b --- /dev/null +++ b/app/imports/api/engine/computation/utility/evaluateCalculation.js @@ -0,0 +1,19 @@ +import resolve, { toString } from '/imports/parser/resolve.js'; + +export default function evaluateCalculation(calculation, scope){ + const parseNode = calculation.parseNode; + const fn = calculation._parseLevel; + const calculationScope = {...calculation._localScope, ...scope}; + const {result: resultNode, context} = resolve(fn, parseNode, calculationScope); + calculation.errors = context.errors; + if (resultNode?.parseType === 'constant'){ + calculation.value = resultNode.value; + } else if (resultNode?.parseType === 'error'){ + calculation.value = null; + } else { + calculation.value = toString(resultNode); + } + // remove the working fields + delete calculation._parseLevel; + delete calculation._localScope; +} diff --git a/app/imports/api/engine/computation/writeComputation/writeScope.js b/app/imports/api/engine/computation/writeComputation/writeScope.js new file mode 100644 index 00000000..3cb0027b --- /dev/null +++ b/app/imports/api/engine/computation/writeComputation/writeScope.js @@ -0,0 +1,5 @@ +import Creatures from '/imports/api/creature/creatures/Creatures.js'; + +export default function writeScope(creatureId, scope){ + Creatures.update(creatureId, {$set: {variables: scope}}); +} diff --git a/app/imports/api/engine/computeCreature.js b/app/imports/api/engine/computeCreature.js index 9d404289..58fe1ed6 100644 --- a/app/imports/api/engine/computeCreature.js +++ b/app/imports/api/engine/computeCreature.js @@ -1,11 +1,13 @@ import buildCreatureComputation from './computation/buildCreatureComputation.js'; import computeCreatureComputation from './computation/computeCreatureComputation.js'; import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties.js'; +import writeScope from './computation/writeComputation/writeScope.js'; export default function computeCreature(creatureId){ const computation = buildCreatureComputation(creatureId); computeCreatureComputation(computation); writeAlteredProperties(computation); + writeScope(creatureId, computation.scope); } // For now just recompute the whole creature, TODO only recompute a single diff --git a/app/imports/api/engine/oldActions/applyAction.js b/app/imports/api/engine/oldActions/applyAction.js deleted file mode 100644 index 1611a764..00000000 --- a/app/imports/api/engine/oldActions/applyAction.js +++ /dev/null @@ -1,13 +0,0 @@ -import spendResources from '/imports/api/creature/actions/spendResources.js' - -export default function applyAction({prop, log}){ - let content = { name: prop.name }; - /* - if (prop.summary){ - content.value = embedInlineCalculations( - prop.summary, prop.summaryCalculations - ); - }*/ - log.content.push(content); - spendResources({prop, log}); -} diff --git a/app/imports/api/engine/oldActions/applyAttack.js b/app/imports/api/engine/oldActions/applyAttack.js index fb157ec5..bdf96ffe 100644 --- a/app/imports/api/engine/oldActions/applyAttack.js +++ b/app/imports/api/engine/oldActions/applyAttack.js @@ -1,4 +1,4 @@ -import roll from '/imports/parser/roll.js'; +import rollDice from '/imports/parser/rollDice.js'; export default function applyAttack({ prop, @@ -6,7 +6,7 @@ export default function applyAttack({ actionContext, creature, }){ - let value = roll(1, 20)[0]; + let value = rollDice(1, 20)[0]; actionContext.attackRoll = {value}; let criticalHitTarget = creature.variables.criticalHitTarget && creature.variables.criticalHitTarget.value || 20; diff --git a/app/imports/api/engine/oldActions/applyProperties.js b/app/imports/api/engine/oldActions/applyProperties.js index e4e72c47..74de8057 100644 --- a/app/imports/api/engine/oldActions/applyProperties.js +++ b/app/imports/api/engine/oldActions/applyProperties.js @@ -7,8 +7,8 @@ import applyRoll from '/imports/api/creature/actions/applyRoll.js'; import applyToggle from '/imports/api/creature/actions/applyToggle.js'; import applySave from '/imports/api/creature/actions/applySave.js'; -function applyProperty(options){ - let prop = options.prop; +function applyProperty(args){ + let prop = args.prop; if (prop.type === 'buff'){ // ignore only applied buffs, don't apply them again if (prop.applied === true){ @@ -26,28 +26,27 @@ function applyProperty(options){ switch (prop.type){ case 'action': case 'spell': - applyAction(options); - break; - case 'attack': - applyAction(options); - applyAttack(options); + if (prop.attackRoll && prop.attackRoll.calculation){ + applyAttack(args) + } + applyAction(args); break; case 'damage': - applyDamage(options); + applyDamage(args); break; case 'adjustment': - applyAdjustment(options); + applyAdjustment(args); break; case 'buff': - applyBuff(options); + applyBuff(args); return false; case 'toggle': - return applyToggle(options); + return applyToggle(args); case 'roll': - applyRoll(options); + applyRoll(args); break; case 'savingThrow': - return applySave(options); + return applySave(args); } return true; } diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index fef5eb67..ccda82d1 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -204,12 +204,6 @@ const ComputedOnlyActionSchema = createPropertySchema({ optional: true, removeBeforeCompute: true, }, - 'resources.attributesConsumed.$.statId': { - type: String, - regEx: SimpleSchema.RegEx.Id, - optional: true, - removeBeforeCompute: true, - }, 'resources.attributesConsumed.$.statName': { type: String, optional: true, diff --git a/app/imports/api/properties/Adjustments.js b/app/imports/api/properties/Adjustments.js index b4825de0..49c1b248 100644 --- a/app/imports/api/properties/Adjustments.js +++ b/app/imports/api/properties/Adjustments.js @@ -14,11 +14,10 @@ const AdjustmentSchema = createPropertySchema({ // Who this adjustment applies to target: { type: String, - defaultValue: 'every', + defaultValue: 'target', allowedValues: [ - 'self', // the character who took the Adjustment - 'each', // rolled once for `each` target - 'every', // rolled once and applied to `every` target + 'self', + 'target', ], }, // The stat this rolls applies to diff --git a/app/imports/api/properties/Branch.js b/app/imports/api/properties/Branch.js new file mode 100644 index 00000000..f1222fc6 --- /dev/null +++ b/app/imports/api/properties/Branch.js @@ -0,0 +1,49 @@ +import SimpleSchema from 'simpl-schema'; +import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; + +let BranchSchema = createPropertySchema({ + branchType: { + type: String, + allowedValues: [ + // Uses the condition field to determine whether to apply children + 'if', + // Attack + 'hit', + 'miss', + // Save + 'failedSave', + 'successfulSave', + // Iterate through targets + 'eachTarget', + // if it has option children, asks to select one + // Otherwise presents its own text with yes/no + //'choice', + //'option', + ], + }, + text: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + condition: { + type: 'fieldToCompute', + optional: true, + parseLevel: 'compile', + }, +}); + +let ComputedOnlyBranchSchema = createPropertySchema({ + condition: { + type: 'computedOnlyField', + optional: true, + parseLevel: 'compile', + }, +}); + +const ComputedBranchSchema = new SimpleSchema() + .extend(BranchSchema) + .extend(ComputedOnlyBranchSchema); + +export { BranchSchema, ComputedBranchSchema, ComputedOnlyBranchSchema } diff --git a/app/imports/api/properties/Buffs.js b/app/imports/api/properties/Buffs.js index ff13bd96..2c169972 100644 --- a/app/imports/api/properties/Buffs.js +++ b/app/imports/api/properties/Buffs.js @@ -25,9 +25,8 @@ let BuffSchema = createPropertySchema({ target: { type: String, allowedValues: [ - 'self', // the character who took the buff - 'each', // rolled once for `each` target - 'every', // rolled once and applied to `every` target + 'self', + 'target', ], defaultValue: 'every', }, diff --git a/app/imports/api/properties/Damages.js b/app/imports/api/properties/Damages.js index bc790b13..80464696 100644 --- a/app/imports/api/properties/Damages.js +++ b/app/imports/api/properties/Damages.js @@ -16,9 +16,8 @@ const DamageSchema = createPropertySchema({ type: String, defaultValue: 'every', allowedValues: [ - 'self', // the character who took the action - 'each', // rolled once for `each` target - 'every', // rolled once and applied to `every` target + 'self', + 'target', ], }, damageType: { diff --git a/app/imports/api/properties/SavingThrows.js b/app/imports/api/properties/SavingThrows.js index 4c96b558..70fc8485 100644 --- a/app/imports/api/properties/SavingThrows.js +++ b/app/imports/api/properties/SavingThrows.js @@ -20,9 +20,8 @@ let SavingThrowSchema = createPropertySchema({ type: String, defaultValue: 'every', allowedValues: [ - 'self', // the character who took the action - 'each', // rolled once for `each` target - 'every', // rolled once and applied to `every` target + 'self', + 'target', ], }, // The variable name of save to roll diff --git a/app/imports/migrations/server/2.0-beta.33-dbv1.js b/app/imports/migrations/server/2.0-beta.33-dbv1.js index eced0aac..f5ceffb3 100644 --- a/app/imports/migrations/server/2.0-beta.33-dbv1.js +++ b/app/imports/migrations/server/2.0-beta.33-dbv1.js @@ -71,6 +71,7 @@ const transformsByPropType = { 'action': actionTransforms, 'adjustment': [ ...getComputedPropertyTransforms('amount'), + {from: 'target', to: 'target', up: simplifyTarget}, ], 'attack': [ ...actionTransforms, @@ -89,6 +90,7 @@ const transformsByPropType = { ...getComputedPropertyTransforms('duration'), ...getInlineComputationTransforms('description'), {from: 'value', to: 'total', up: nanToNull}, + {from: 'target', to: 'target', up: simplifyTarget}, ], 'classLevel': [ ...getInlineComputationTransforms('description'), @@ -98,6 +100,7 @@ const transformsByPropType = { ], 'damage': [ ...getComputedPropertyTransforms('amount'), + {from: 'target', to: 'target', up: simplifyTarget}, ], 'effect': [ {from: 'calculation', to: 'amount.calculation'}, @@ -128,6 +131,7 @@ const transformsByPropType = { ], 'savingThrow': [ ...getComputedPropertyTransforms('dc'), + {from: 'target', to: 'target', up: simplifyTarget}, ], 'skill': [ ...getComputedPropertyTransforms('baseValue'), @@ -193,6 +197,14 @@ function stripZero(val){ } } +function simplifyTarget(val){ + if (val === 'self'){ + return val; + } else { + return 'target'; + } +} + function trimErrors(arr){ if(!arr) return arr; arr.forEach(e => { diff --git a/app/imports/parser/parseTree/array.js b/app/imports/parser/parseTree/array.js index 7ee711a6..fd1922c8 100644 --- a/app/imports/parser/parseTree/array.js +++ b/app/imports/parser/parseTree/array.js @@ -1,4 +1,4 @@ -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; import constant from './constant.js'; const array = { @@ -41,6 +41,13 @@ const array = { fn(node); node.values.forEach(value => traverse(value, fn)); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.values = node.values.map(value => map(value, fn)); + } + return resultingNode; + }, } export default array; diff --git a/app/imports/parser/parseTree/call.js b/app/imports/parser/parseTree/call.js index 52b16967..83cce223 100644 --- a/app/imports/parser/parseTree/call.js +++ b/app/imports/parser/parseTree/call.js @@ -1,7 +1,7 @@ import error from './error.js'; import constant from './constant.js'; import functions from '/imports/parser/functions.js'; -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; const call = { create({functionName, args}) { @@ -104,6 +104,13 @@ const call = { fn(node); node.args.forEach(arg => traverse(arg, fn)); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.args = node.args.map(arg => map(arg, fn)); + } + return resultingNode; + }, checkArugments({node, fn, argumentsExpected, resolvedArgs, context}){ // Check that the number of arguments matches the number expected if ( diff --git a/app/imports/parser/parseTree/if.js b/app/imports/parser/parseTree/if.js index fc56bf66..126b0463 100644 --- a/app/imports/parser/parseTree/if.js +++ b/app/imports/parser/parseTree/if.js @@ -1,4 +1,4 @@ -import resolve, { traverse, toString } from '../resolve'; +import resolve, { traverse, toString, map } from '../resolve'; const ifNode = { create({condition, consequent, alternative}){ @@ -39,6 +39,15 @@ const ifNode = { traverse(node.consequent, fn); traverse(node.alternative, fn); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.condition = map(node.condition, fn); + node.consequent = map(node.consequent, fn); + node.alternative = map(node.alternative, fn); + } + return resultingNode; + }, } export default ifNode; diff --git a/app/imports/parser/parseTree/index.js b/app/imports/parser/parseTree/index.js index 8fbea29d..71fd151b 100644 --- a/app/imports/parser/parseTree/index.js +++ b/app/imports/parser/parseTree/index.js @@ -1,4 +1,4 @@ -import resolve, { traverse, toString } from '../resolve'; +import resolve, { traverse, toString, map } from '../resolve'; import error from './error'; const indexNode = { @@ -68,6 +68,14 @@ const indexNode = { traverse(node.array, fn); traverse(node.index, fn); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.array = map(node.array, fn); + node.index = map(node.index, fn); + } + return resultingNode; + }, } export default indexNode; diff --git a/app/imports/parser/parseTree/not.js b/app/imports/parser/parseTree/not.js index 9488a96c..6864f5eb 100644 --- a/app/imports/parser/parseTree/not.js +++ b/app/imports/parser/parseTree/not.js @@ -1,4 +1,4 @@ -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; import constant from './constant.js'; const not = { @@ -31,7 +31,14 @@ const not = { traverse(node, fn){ fn(node); traverse(node.right, fn); - } + }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.right = map(node.right, fn); + } + return resultingNode; + }, } export default not; diff --git a/app/imports/parser/parseTree/operator.js b/app/imports/parser/parseTree/operator.js index 7cc4a099..5e827e9a 100644 --- a/app/imports/parser/parseTree/operator.js +++ b/app/imports/parser/parseTree/operator.js @@ -1,4 +1,4 @@ -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; import constant from './constant.js'; const operator = { @@ -71,6 +71,14 @@ const operator = { traverse(node.left, fn); traverse(node.right, fn); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.left = map(node.left, fn); + node.right = map(node.right, fn); + } + return resultingNode; + }, } export default operator; diff --git a/app/imports/parser/parseTree/parenthesis.js b/app/imports/parser/parseTree/parenthesis.js index cd0cfa1a..f66e4364 100644 --- a/app/imports/parser/parseTree/parenthesis.js +++ b/app/imports/parser/parseTree/parenthesis.js @@ -1,4 +1,4 @@ -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; const parenthesis = { create({content}) { @@ -28,7 +28,14 @@ const parenthesis = { traverse(node, fn){ fn(node); traverse(node.content, fn); - } + }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.content = map(node.content, fn); + } + return resultingNode; + }, } export default parenthesis; diff --git a/app/imports/parser/parseTree/roll.js b/app/imports/parser/parseTree/roll.js index cc87cbcc..09817d8f 100644 --- a/app/imports/parser/parseTree/roll.js +++ b/app/imports/parser/parseTree/roll.js @@ -1,7 +1,7 @@ -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; import error from './error.js'; import rollArray from './rollArray.js'; -import roll from '/imports/parser/roll.js'; +import rollDice from '/imports/parser/rollDice.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; const rollNode = { @@ -39,7 +39,7 @@ const rollNode = { return errorResult('Dice size is not an integer', node, context); } let number = left.value; - if (context.doubleRolls){ + if (context.options.doubleRolls){ number *= 2; } if (number > STORAGE_LIMITS.diceRollValuesCount){ @@ -47,7 +47,7 @@ const rollNode = { return errorResult(message, node, context); } let diceSize = right.value; - let values = roll(number, diceSize); + let values = rollDice(number, diceSize); if (context){ context.storeRoll({number, diceSize, values}); } @@ -69,6 +69,14 @@ const rollNode = { traverse(node.left, fn); traverse(node.right, fn); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.left = map(node.left, fn); + node.right = map(node.right, fn); + } + return resultingNode; + }, } function errorResult(message, node, context){ diff --git a/app/imports/parser/parseTree/rollArray.js b/app/imports/parser/parseTree/rollArray.js index bb778947..158e101e 100644 --- a/app/imports/parser/parseTree/rollArray.js +++ b/app/imports/parser/parseTree/rollArray.js @@ -27,9 +27,6 @@ const rollArray = { context, }; }, - traverse(node, fn){ - return fn(node); - } } export default rollArray; diff --git a/app/imports/parser/parseTree/unaryOperator.js b/app/imports/parser/parseTree/unaryOperator.js index 1b348f90..c8aaebe1 100644 --- a/app/imports/parser/parseTree/unaryOperator.js +++ b/app/imports/parser/parseTree/unaryOperator.js @@ -1,4 +1,4 @@ -import resolve, { toString, traverse } from '../resolve.js'; +import resolve, { toString, traverse, map } from '../resolve.js'; import constant from './constant.js'; const unaryOperator = { @@ -41,6 +41,13 @@ const unaryOperator = { fn(node); traverse(node.right, fn); }, + map(node, fn){ + const resultingNode = fn(node); + if (resultingNode === node){ + node.right = map(node.right, fn); + } + return resultingNode; + }, }; export default unaryOperator; diff --git a/app/imports/parser/resolve.js b/app/imports/parser/resolve.js index 2663dbbd..7089901d 100644 --- a/app/imports/parser/resolve.js +++ b/app/imports/parser/resolve.js @@ -43,11 +43,24 @@ export function traverse(node, fn){ return fn(node); } +export function map(node, fn){ + if (!node) return; + let type = nodeTypeIndex[node.parseType]; + if (!type){ + console.error(node); + throw new Meteor.Error('Not valid parse node'); + } + if (type.map){ + return type.map(node, fn); + } + return fn(node); +} + export class Context { - constructor({errors = [], rolls = [], doubleRolls} = {}){ + constructor({errors = [], rolls = [], options = {}} = {}){ this.errors = errors; this.rolls = rolls; - this.doubleRolls = doubleRolls; + this.options = options; } error(e){ if (!e) return; diff --git a/app/imports/parser/roll.js b/app/imports/parser/rollDice.js similarity index 79% rename from app/imports/parser/roll.js rename to app/imports/parser/rollDice.js index 382a4cad..4e379ba6 100644 --- a/app/imports/parser/roll.js +++ b/app/imports/parser/rollDice.js @@ -1,4 +1,4 @@ -export default function roll(number, diceSize){ +export default function rollDice(number, diceSize){ let values = []; let randomSrc = DDP.randomStream('diceRoller'); for (let i = 0; i < number; i++){ diff --git a/app/imports/ui/properties/forms/SkillForm.vue b/app/imports/ui/properties/forms/SkillForm.vue index eb4ee772..0bfe5ebb 100644 --- a/app/imports/ui/properties/forms/SkillForm.vue +++ b/app/imports/ui/properties/forms/SkillForm.vue @@ -28,6 +28,7 @@