From 51d3fbbcb7fc24ba96a10b7ddaf2ca06c9339fc3 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sun, 10 Oct 2021 19:44:02 +0200 Subject: [PATCH] Completed first pass at action system re-write. Untested --- .../applyPropertyByType/applyAction.js | 11 +- .../applyPropertyByType}/applyDamage.js | 79 +++------- .../actions/applyPropertyByType/applyRoll.js | 21 +++ .../applyPropertyByType/applySavingThrow.js | 78 ++++++++++ .../applyPropertyByType/applyToggle.js | 14 ++ .../shared/recalculateCalculation.js | 4 +- app/imports/api/engine/actions/doAction.js | 137 ++++++++++-------- .../methods/commitAction.js} | 47 +----- .../utility/evaluateCalculation.js | 4 +- .../api/engine/oldActions/applyAdjustment.js | 55 ------- .../api/engine/oldActions/applyAttack.js | 22 --- .../api/engine/oldActions/applyBuff.js | 61 -------- .../api/engine/oldActions/applyProperties.js | 81 ----------- .../api/engine/oldActions/applyRoll.js | 25 ---- .../api/engine/oldActions/applySave.js | 76 ---------- .../api/engine/oldActions/applyToggle.js | 33 ----- .../engine/oldActions/castSpellWithSlot.js | 92 ------------ app/imports/api/engine/oldActions/doCheck.js | 56 ------- .../engine/oldActions/getAncestorContext.js | 15 -- .../api/engine/oldActions/spendResources.js | 93 ------------ .../properties/subSchemas/ResourcesSchema.js | 68 --------- 21 files changed, 232 insertions(+), 840 deletions(-) rename app/imports/api/engine/{oldActions => actions/applyPropertyByType}/applyDamage.js (54%) create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyRoll.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyToggle.js rename app/imports/api/engine/{oldActions/doAction.js => actions/methods/commitAction.js} (57%) delete mode 100644 app/imports/api/engine/oldActions/applyAdjustment.js delete mode 100644 app/imports/api/engine/oldActions/applyAttack.js delete mode 100644 app/imports/api/engine/oldActions/applyBuff.js delete mode 100644 app/imports/api/engine/oldActions/applyProperties.js delete mode 100644 app/imports/api/engine/oldActions/applyRoll.js delete mode 100644 app/imports/api/engine/oldActions/applySave.js delete mode 100644 app/imports/api/engine/oldActions/applyToggle.js delete mode 100644 app/imports/api/engine/oldActions/castSpellWithSlot.js delete mode 100644 app/imports/api/engine/oldActions/doCheck.js delete mode 100644 app/imports/api/engine/oldActions/getAncestorContext.js delete mode 100644 app/imports/api/engine/oldActions/spendResources.js delete mode 100644 app/imports/api/properties/subSchemas/ResourcesSchema.js diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index b2507456..08cfb17e 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -35,6 +35,9 @@ export default function applyAction(node, {creature, targets, scope, log}){ function applyAttackWithoutTarget({prop, scope, log}){ delete scope['$attackHit']; delete scope['$attackMiss']; + delete scope['$criticalHit']; + delete scope['$criticalMiss']; + delete scope['$attackRoll']; recalculateCalculation(prop.rollBonus, scope, log); @@ -54,18 +57,22 @@ function applyAttackWithoutTarget({prop, scope, log}){ function applyAttackToTarget({prop, target, scope, log}){ delete scope['$attackHit']; delete scope['$attackMiss']; + delete scope['$criticalHit']; + delete scope['$criticalMiss']; + delete scope['$attackDiceRoll']; + delete scope['$attackRoll']; recalculateCalculation(prop.rollBonus, scope, log); const value = rollDice(1, 20)[0]; - scope['$attackRoll'] = {value}; + scope['$attackDiceRoll'] = {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}; + scope['$attackRoll'] = {value: result}; if (target.variables.armor){ const armor = target.variables.armor.value; const name = criticalHit ? 'Critical Hit!' : diff --git a/app/imports/api/engine/oldActions/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js similarity index 54% rename from app/imports/api/engine/oldActions/applyDamage.js rename to app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index 0e253050..6131da80 100644 --- a/app/imports/api/engine/oldActions/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -1,51 +1,32 @@ -// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +import applyProperty from '../applyProperty.js'; import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js'; import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; -import { CompilationContext } from '/imports/parser/parser.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; +import { Context } from '/imports/parser/resolve.js'; -export default function applyDamage({ - prop, - creature, - targets, - actionContext, - log, +export default function applyDamage(node, { + creature, targets, scope, log }){ - let damageTargets = prop.target === 'self' ? [creature] : targets; - let scope = { - ...creature.variables, - ...actionContext, + const applyChildren = function(){ + node.children.forEach(child => applyProperty(child, { + creature, targets, scope, log + })); }; - // Add the target's variables to the scope - if (targets.length === 1){ - scope.target = targets[0].variables; - } + + const prop = node.node; + let damageTargets = prop.target === 'self' ? [creature] : targets; // Determine if the hit is critical - let criticalHit = !!( - actionContext.criticalHit && - actionContext.criticalHit.value && + let criticalHit = scope['$criticalHit']?.value && prop.damageType !== 'healing' // Can't critically heal - ); + ; // Double the damage rolls if the hit is critical - let context = new CompilationContext({ - doubleRolls: criticalHit, + let context = new Context({ + options: {doubleRolls: criticalHit}, }); + recalculateCalculation(prop.amount, scope, log, context); - // Compute the roll the first time, logging any errors - var {result} = evaluateString({ - string: prop.amount, - scope, - fn: 'reduce', - context - }); - - // If the result is an error bail out now - if (result.constructor.name === 'ErrorNode'){ - log.content.push({ - name: 'Damage error', - value: result.toString(), - }); - return; - } + // If we didn't end up with a finite amount, give up + if (!isFinite(prop.amount?.value)) return applyChildren(); // Memoise the damage suffix for the log let suffix = (criticalHit ? ' critical ' : ' ') + @@ -57,28 +38,11 @@ export default function applyDamage({ damageTargets.forEach(target => { let name = prop.damageType === 'healing' ? 'Healing' : 'Damage'; - // Reroll the damage if needed - if (prop.target === 'each'){ - ({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', - value: result.toString(), - }); - return; - } - // Deal the damage to the target let damageDealt = dealDamage.call({ creatureId: target._id, damageType: prop.damageType, - amount: result.value, + amount: prop.amount.value, }); // Log the damage done @@ -109,7 +73,8 @@ export default function applyDamage({ // There are no targets, just log the result log.content.push({ name: prop.damageType === 'healing' ? 'Healing' : 'Damage', - value: result.toString() + suffix, + value: prop.amount.value + suffix, }); } + return applyChildren(); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js new file mode 100644 index 00000000..05996784 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js @@ -0,0 +1,21 @@ +import applyProperty from '../applyProperty.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; + +export default function applyRoll(node, {creature, targets, scope, log}){ + const prop = node.node; + + if (prop.roll?.calculation){ + recalculateCalculation(prop.roll, scope, log, context); + + if (isFinite(prop.roll.value)){ + scope[prop.variableName] = prop.roll.value; + } + log.content.push({ + name: prop.name, + value: prop.variableName + ' = ' + prop.roll + ' = ' + prop.roll.value, + }); + } + return node.children.forEach(child => applyProperty(child, { + creature, targets, scope, log + })); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js new file mode 100644 index 00000000..1e0ad6f6 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -0,0 +1,78 @@ +import rollDice from '/imports/parser/rollDice.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; +import applyProperty from '../applyProperty.js'; + +export default function applySavingThrow(node, {creature, targets, scope, log}){ + let saveTargets = prop.target === 'self' ? [creature] : targets; + + const prop = node.node; + + recalculateCalculation(prop.dc, scope, log, context); + + const dc = (prop.dc?.value); + if (!isFinite(dc)){ + log.content.push({ + name: 'Error', + value: 'Saving throw requires a DC', + }); + return node.children.forEach(child => applyProperty(child, { + creature, targets, scope, log + })); + } + log.content.push({ + name: prop.name, + value: ' DC ' + dc, + }); + + saveTargets.forEach(target => { + delete scope['$saveFailed']; + delete scope['$saveSucceeded']; + delete scope['$saveDiceRoll']; + delete scope['$saveRoll']; + + const applyChildren = function(){ + node.children.forEach(child => applyProperty(child, { + creature, targets: [target], scope, log + })); + }; + + const save = target.variables[prop.stat]; + + if (!save){ + log.content.push({ + name: 'Saving throw error', + value: 'No saving throw found: ' + prop.stat, + }); + return applyChildren(); + } + + let value, values, resultPrefix; + if (save.advantage === 1){ + values = rollDice(2, 20).sort().reverse(); + value = values[0]; + resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = ` + } else if (save.advantage === -1){ + values = rollDice(2, 20).sort(); + value = values[0]; + resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = ` + } else { + values = rollDice(1, 20); + value = values[0]; + resultPrefix = `1d20 [${value}] + ${save.value} = ` + } + scope['$saveDiceRoll'] = {value}; + const result = value + save.value || 0; + scope['$saveRoll'] = {value: result}; + const saveSuccess = result >= dc; + if (saveSuccess){ + scope['$saveSucceeded'] = {value: true}; + } else { + scope['$saveFailed'] = {value: true}; + } + log.content.push({ + name: 'Save', + value: resultPrefix + result + (saveSuccess ? 'Passed' : 'Failed') + }); + return applyChildren(); + }); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js b/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js new file mode 100644 index 00000000..5162c41f --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js @@ -0,0 +1,14 @@ +import applyProperty from '../applyProperty.js'; +import recalculateCalculation from './shared/recalculateCalculation.js'; + +export default function applyToggle(node, { + creature, targets, scope, log +}){ + const prop = node.node; + recalculateCalculation(prop.condition, scope, log); + if (prop.condition?.value) { + return node.children.forEach(child => applyProperty(child, { + creature, targets, scope, log + })); + } +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js index cdc541fe..b58666b0 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js +++ b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js @@ -1,9 +1,9 @@ import evaluateCalculation from '../utility/evaluateCalculation.js'; import logErrors from './logErrors.js'; -export default function recalculateCalculation(calc, scope, log){ +export default function recalculateCalculation(calc, scope, log, context){ if (!calc.parseNode) return; calc._parseLevel = 'reduce'; - evaluateCalculation(calc, scope); + evaluateCalculation(calc, scope, context); logErrors(calc.errors, log); } diff --git a/app/imports/api/engine/actions/doAction.js b/app/imports/api/engine/actions/doAction.js index 8f0bab5e..8643e154 100644 --- a/app/imports/api/engine/actions/doAction.js +++ b/app/imports/api/engine/actions/doAction.js @@ -1,15 +1,90 @@ +import SimpleSchema from 'simpl-schema'; +import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; +import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; 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 { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.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}){ +const doAction = new ValidatedMethod({ + name: 'creatureProperties.doAction', + validate: new SimpleSchema({ + actionId: SimpleSchema.RegEx.Id, + targetIds: { + type: Array, + defaultValue: [], + maxCount: 20, + optional: true, + }, + 'targetIds.$': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 10, + timeInterval: 5000, + }, + run({actionId, targetIds = []}) { + let action = CreatureProperties.findOne(actionId); + // Check permissions + let creature = getRootCreatureAncestor(action); + + assertEditPermission(creature, this.userId); + + // Get all the targets and make sure we can edit them + let targets = []; + targetIds.forEach(targetId => { + let target = Creatures.findOne(targetId); + assertEditPermission(target, this.userId); + targets.push(target); + }); + + // 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}, + }); + + // Get cursor of the properties + const properties = CreatureProperties.find({ + $or: [{_id: action._id}, {'ancestors.id': action._id}], + removed: {$ne: true}, + }, { + sort: {order: 1}, + }); + + // Do the action + doActionWork({creature, targets, properties, ancestors, method: this}); + + // Recompute all involved creatures + Meteor.defer(() => computeCreature(creature._id)); + targets.forEach(target => { + Meteor.defer(() => computeCreature(target._id)); + }); + }, +}); + +export default doAction; + +export function doActionWork({ + creature, targets, properties, ancestors, method +}){ // get the docs - const { - creature, targets, properties, ancestors - } = fetchActionDocs(actionId, targetIds); const ancestorScope = getAncestorScope(ancestors); const propertyForest = nodeArrayToTree(properties); if (propertyForest.length !== 1){ @@ -37,60 +112,6 @@ export default function doAction({actionId, targetIds, method}){ // 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 diff --git a/app/imports/api/engine/oldActions/doAction.js b/app/imports/api/engine/actions/methods/commitAction.js similarity index 57% rename from app/imports/api/engine/oldActions/doAction.js rename to app/imports/api/engine/actions/methods/commitAction.js index cfea79e2..472ca1d8 100644 --- a/app/imports/api/engine/oldActions/doAction.js +++ b/app/imports/api/engine/actions/methods/commitAction.js @@ -3,22 +3,19 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import Creatures from '/imports/api/creature/creatures/Creatures.js'; -import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; import computeCreature from '/imports/api/engine/computeCreature.js'; -import nodesToTree from '/imports/api/parenting/nodesToTree.js'; -import applyProperties from '/imports/api/creature/actions/applyProperties.js'; -import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js'; +import doAction from '../doAction.js'; -const doAction = new ValidatedMethod({ +const commitAction = new ValidatedMethod({ name: 'creatureProperties.doAction', validate: new SimpleSchema({ actionId: SimpleSchema.RegEx.Id, targetIds: { type: Array, defaultValue: [], - maxCount: 10, + maxCount: 20, optional: true, }, 'targetIds.$': { @@ -36,9 +33,6 @@ const doAction = new ValidatedMethod({ // Check permissions let creature = getRootCreatureAncestor(action); - // Build ancestor context - let actionContext = getAncestorContext(action); - assertEditPermission(creature, this.userId); let targets = []; targetIds.forEach(targetId => { @@ -46,7 +40,7 @@ const doAction = new ValidatedMethod({ assertEditPermission(target, this.userId); targets.push(target); }); - doActionWork({action, creature, targets, actionContext, method: this}); + doAction({action, creature, targets, method: this}); // recompute creatures computeCreature(creature._id); @@ -57,35 +51,4 @@ const doAction = new ValidatedMethod({ }, }); -export function doActionWork({ - action, - creature, - targets, - actionContext = {}, - method -}){ - // Create the log - let log = CreatureLogSchema.clean({ - creatureId: creature._id, - creatureName: creature.name, - }); - - let decendantForest = nodesToTree({ - collection: CreatureProperties, - ancestorId: action._id, - }); - let startingForest = [{ - node: action, - children: decendantForest, - }]; - applyProperties({ - forest: startingForest, - actionContext, - creature, - targets, - log, - }); - insertCreatureLogWork({log, creature, method}); -} - -export default doAction; +export default commitAction; diff --git a/app/imports/api/engine/computation/utility/evaluateCalculation.js b/app/imports/api/engine/computation/utility/evaluateCalculation.js index d88f424b..580cafde 100644 --- a/app/imports/api/engine/computation/utility/evaluateCalculation.js +++ b/app/imports/api/engine/computation/utility/evaluateCalculation.js @@ -1,10 +1,10 @@ import resolve, { toString } from '/imports/parser/resolve.js'; -export default function evaluateCalculation(calculation, scope){ +export default function evaluateCalculation(calculation, scope, givenContext){ const parseNode = calculation.parseNode; const fn = calculation._parseLevel; const calculationScope = {...calculation._localScope, ...scope}; - const {result: resultNode, context} = resolve(fn, parseNode, calculationScope); + const {result: resultNode, context} = resolve(fn, parseNode, calculationScope, givenContext); calculation.errors = context.errors; if (resultNode?.parseType === 'constant'){ calculation.value = resultNode.value; diff --git a/app/imports/api/engine/oldActions/applyAdjustment.js b/app/imports/api/engine/oldActions/applyAdjustment.js deleted file mode 100644 index 5e0f50a3..00000000 --- a/app/imports/api/engine/oldActions/applyAdjustment.js +++ /dev/null @@ -1,55 +0,0 @@ -// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; -import damagePropertiesByName from '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js'; - -export default function applyAdjustment({ - prop, - creature, - targets, - actionContext, - log -}){ - let damageTargets = prop.target === 'self' ? [creature] : targets; - let scope = { - ...creature.variables, - ...actionContext, - }; - var {result, context} = evaluateString({ - string: prop.amount, - scope, - fn: 'reduce' - }); - context.errors.forEach(e => { - log.content.push({ - name: 'Attribute damage error', - value: e.message || e.toString(), - }); - }); - if (damageTargets) { - damageTargets.forEach(target => { - if (prop.target === 'each'){ - ({result} = evaluateString({ - string: prop.amount, - scope, - fn: 'reduce' - })); - } - damagePropertiesByName.call({ - creatureId: target._id, - variableName: prop.stat, - operation: prop.operation || 'increment', - value: result.value, - }); - log.content.push({ - name: 'Attribute damage', - value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + - ` ${result.isNumber ? -result.value : result.toString()}`, - }); - }); - } else { - log.content.push({ - name: 'Attribute damage', - value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + - ` ${result.isNumber ? -result.value : result.toString()}`, - }); - } -} diff --git a/app/imports/api/engine/oldActions/applyAttack.js b/app/imports/api/engine/oldActions/applyAttack.js deleted file mode 100644 index bdf96ffe..00000000 --- a/app/imports/api/engine/oldActions/applyAttack.js +++ /dev/null @@ -1,22 +0,0 @@ -import rollDice from '/imports/parser/rollDice.js'; - -export default function applyAttack({ - prop, - log, - actionContext, - creature, -}){ - let value = rollDice(1, 20)[0]; - actionContext.attackRoll = {value}; - let criticalHitTarget = creature.variables.criticalHitTarget && - creature.variables.criticalHitTarget.value || 20; - let criticalHit = value >= criticalHitTarget; - if (criticalHit) actionContext.criticalHit = {value: true}; - let result = value + prop.rollBonusResult; - actionContext.toHit = {value: result}; - - log.content.push({ - name: criticalHit ? 'Critical Hit!' : 'To Hit', - value: `1d20 [${value}] + ${prop.rollBonusResult} = ` + result, - }); -} diff --git a/app/imports/api/engine/oldActions/applyBuff.js b/app/imports/api/engine/oldActions/applyBuff.js deleted file mode 100644 index 2bde6c85..00000000 --- a/app/imports/api/engine/oldActions/applyBuff.js +++ /dev/null @@ -1,61 +0,0 @@ -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'; - -export default function applyBuff({ - prop, - children, - creature, - targets = [], - //actionContext, -}){ - let buffTargets = prop.target === 'self' ? [creature] : targets; - - //let scope = { - // ...creature.variables, - // ...actionContext, - //}; - - // TODO - // If the target is not self, walk through all decendants and replace - // variables in calculations with their values from the creature scope - // If the target is self, replace all the target.x references with just x - - // 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(children); - let oldParent = { - id: prop.parent.id, - collection: prop.parent.collection, - }; - buffTargets.forEach(target => { - copyNodeListToTarget(propList, target, oldParent); - }); -} - -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); -} diff --git a/app/imports/api/engine/oldActions/applyProperties.js b/app/imports/api/engine/oldActions/applyProperties.js deleted file mode 100644 index 74de8057..00000000 --- a/app/imports/api/engine/oldActions/applyProperties.js +++ /dev/null @@ -1,81 +0,0 @@ -import applyAction from '/imports/api/creature/actions/applyAction.js'; -import applyAdjustment from '/imports/api/creature/actions/applyAdjustment.js'; -import applyAttack from '/imports/api/creature/actions/applyAttack.js'; -import applyBuff from '/imports/api/creature/actions/applyBuff.js'; -import applyDamage from '/imports/api/creature/actions/applyDamage.js'; -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(args){ - let prop = args.prop; - if (prop.type === 'buff'){ - // ignore only applied buffs, don't apply them again - if (prop.applied === true){ - return false; - } - // Only ignore toggles if they wont be computed - } else if (prop.type === 'toggle') { - if (prop.disabled) return false; - if (prop.enabled) return true; - if (!prop.condition) return false; - // Ignore inactive props of other types - } else if (prop.deactivatedBySelf === true){ - return false; - } - switch (prop.type){ - case 'action': - case 'spell': - if (prop.attackRoll && prop.attackRoll.calculation){ - applyAttack(args) - } - applyAction(args); - break; - case 'damage': - applyDamage(args); - break; - case 'adjustment': - applyAdjustment(args); - break; - case 'buff': - applyBuff(args); - return false; - case 'toggle': - return applyToggle(args); - case 'roll': - applyRoll(args); - break; - case 'savingThrow': - return applySave(args); - } - return true; -} - -function applyPropertyAndWalkChildren({prop, children, targets, ...options}){ - let shouldKeepWalking = applyProperty({ prop, children, targets, ...options }); - if (shouldKeepWalking){ - applyProperties({ forest: children, targets, ...options,}); - } -} - -export default function applyProperties({ forest, targets, ...options}){ - forest.forEach(node => { - let prop = node.node; - options.actionContext[`#${prop.type}`] = prop; - let children = node.children; - if (shouldSplit(prop) && targets.length){ - targets.forEach(target => { - let targets = [target] - applyPropertyAndWalkChildren({ targets, prop, children, ...options}); - }); - } else { - applyPropertyAndWalkChildren({prop, children, targets, ...options}); - } - }); -} - -function shouldSplit(prop){ - if (prop.target === 'each'){ - return true; - } -} diff --git a/app/imports/api/engine/oldActions/applyRoll.js b/app/imports/api/engine/oldActions/applyRoll.js deleted file mode 100644 index b16240e3..00000000 --- a/app/imports/api/engine/oldActions/applyRoll.js +++ /dev/null @@ -1,25 +0,0 @@ -// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; - -export default function applyRoll({ - prop, - creature, - actionContext, - log, -}){ - let scope = { - ...creature.variables, - ...actionContext, - }; - var {result} = evaluateString({ - string: prop.roll, - scope, - fn: 'reduce' - }); - if (result.isNumber){ - actionContext[prop.variableName] = result.value; - } - log.content.push({ - name: prop.name, - value: prop.variableName + ' = ' + prop.roll + ' = ' + result.toString(), - }); -} diff --git a/app/imports/api/engine/oldActions/applySave.js b/app/imports/api/engine/oldActions/applySave.js deleted file mode 100644 index 99b753b1..00000000 --- a/app/imports/api/engine/oldActions/applySave.js +++ /dev/null @@ -1,76 +0,0 @@ -// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; -import CreaturesProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; -import roll from '/imports/parser/roll.js'; - -export default function applySave({ - prop, - creature, - actionContext, - log, -}){ - let scope = { - ...creature.variables, - ...actionContext, - }; - try { - // Calculate the DC - var {result} = evaluateString({ - string: prop.dc, - scope, - fn: 'reduce' - }); - let dc = result.value; - log.content.push({ - name: prop.name, - value: ' DC ' + result.toString(), - }); - if (prop.target === 'self'){ - let save = CreaturesProperties.findOne({ - 'ancestors.id': creature._id, - type: 'skill', - skillType: 'save', - variableName: prop.stat, - removed: {$ne: true}, - inactive: {$ne: true}, - }); - if (!save){ - log.content.push({ - name: 'Saving throw error', - value: 'No saving throw found: ' + prop.stat, - }); - return; - } - let value, values, resultPrefix; - if (save.advantage === 1){ - values = roll(2, 20).sort().reverse(); - value = values[0]; - resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = ` - } else if (save.advantage === -1){ - values = roll(2, 20).sort(); - value = values[0]; - resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = ` - } else { - values = roll(1, 20); - value = values[0]; - resultPrefix = `1d20 [${value}] + ${save.value} = ` - } - actionContext.savingThrowRoll = {value}; - let result = value + save.value; - actionContext.savingThrow = {value: result}; - let saveSuccess = result >= dc; - log.content.push({ - name: 'Save', - value: resultPrefix + result + (saveSuccess ? 'Passed' : 'Failed') - }); - return !saveSuccess; - } else { - // TODO - return true; - } - } catch (e){ - log.content.push({ - name: 'Save error', - value: e.toString(), - }); - } -} diff --git a/app/imports/api/engine/oldActions/applyToggle.js b/app/imports/api/engine/oldActions/applyToggle.js deleted file mode 100644 index 745d9edd..00000000 --- a/app/imports/api/engine/oldActions/applyToggle.js +++ /dev/null @@ -1,33 +0,0 @@ -// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; - -export default function applyToggle({ - prop, - creature, - actionContext, - log, -}){ - let scope = { - ...creature.variables, - ...actionContext, - }; - if (Number.isFinite(+prop.condition)){ - return !!+prop.condition; - } - var {result} = evaluateString({ - string: prop.condition, - scope, - fn: 'reduce' - }); - if (result.constructor.name === 'ErrorNode') { - log.content.push({ - name: 'Toggle error', - value: result.toString(), - }); - return false; - } - log.content.push({ - name: prop.name || 'Toggle', - value: prop.condition + ' = ' + result.toString(), - }); - return !!result.value; -} diff --git a/app/imports/api/engine/oldActions/castSpellWithSlot.js b/app/imports/api/engine/oldActions/castSpellWithSlot.js deleted file mode 100644 index 74ef41c3..00000000 --- a/app/imports/api/engine/oldActions/castSpellWithSlot.js +++ /dev/null @@ -1,92 +0,0 @@ -import SimpleSchema from 'simpl-schema'; -import { ValidatedMethod } from 'meteor/mdg:validated-method'; -import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; -import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; -import Creatures from '/imports/api/creature/creatures/Creatures.js'; -import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; -import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; -import computeCreature from '/imports/api/engine/computeCreature.js'; -import { doActionWork } from '/imports/api/creature/actions/doAction.js'; -import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; -import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js'; -import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory'; -import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties'; - -const castSpellWithSlot = new ValidatedMethod({ - name: 'creatureProperties.castSpellWithSlot', - validate: new SimpleSchema({ - spellId: SimpleSchema.RegEx.Id, - slotId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - optional: true, - }, - targetId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - optional: true, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 10, - timeInterval: 5000, - }, - run({spellId, slotId, targetId}) { - let spell = CreatureProperties.findOne(spellId); - // Check permissions - let creature = getRootCreatureAncestor(spell); - assertEditPermission(creature, this.userId); - let target = undefined; - if (targetId) { - target = Creatures.findOne(targetId); - assertEditPermission(target, this.userId); - } - let slotLevel = spell.level || 0; - if (slotLevel !== 0){ - let slot = CreatureProperties.findOne(slotId); - if (!slot){ - throw new Meteor.Error('No slot', - 'Slot not found to cast spell'); - } - if (!slot.value){ - throw new Meteor.Error('No slot', - 'Slot depleted'); - } - if (!(slot.spellSlotLevelValue >= spell.level)){ - throw new Meteor.Error('Slot too small', - 'Slot is not large enough to cast spell'); - } - slotLevel = slot.spellSlotLevelValue; - damagePropertyWork({ - property: slot, - operation: 'increment', - value: 1, - }); - } - let actionContext = getAncestorContext(spell); - - doActionWork({ - action: spell, - actionContext: {slotLevel, ...actionContext}, - creature, - targets: target ? [target] : [], - method: this, - }); - - // Note these lines only recompute the top-level creature, not the nearest one - // The acting creature might have a new item - recomputeInventory(creature._id); - // The spell might add properties which need to be activated - recomputeInactiveProperties(creature._id); - recomputeCreatureByDoc(creature); - - if (target){ - recomputeInventory(target._id); - recomputeInactiveProperties(target._id); - recomputeCreatureByDoc(target); - } - }, -}); - -export default castSpellWithSlot; diff --git a/app/imports/api/engine/oldActions/doCheck.js b/app/imports/api/engine/oldActions/doCheck.js deleted file mode 100644 index 410462a7..00000000 --- a/app/imports/api/engine/oldActions/doCheck.js +++ /dev/null @@ -1,56 +0,0 @@ -import SimpleSchema from 'simpl-schema'; -import { ValidatedMethod } from 'meteor/mdg:validated-method'; -import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; -import Creatures from '/imports/api/creature/creatures/Creatures.js'; -import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; -import roll from '/imports/parser/roll.js'; - -const doCheck = new ValidatedMethod({ - name: 'creature.doCheck', - validate: new SimpleSchema({ - creatureId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - optional: true, - }, - attributeName: { - type: String, - optional: true, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 10, - timeInterval: 5000, - }, - run({creatureId, attributeName}) { - let creature = Creatures.findOne(creatureId); - assertEditPermission(creature, this.userId); - let bonus = getAttributeValue({creature, attributeName}) - return doCheckWork({bonus}); - }, -}); - -function getAttributeValue({creature, attributeName}){ - let att = creature.variables[attributeName]; - if (!att) throw new Meteor.Error('No such attribute', - `This creature does not have a ${attributeName} property`); - let bonus = att.attributeType === 'ability'? att.modifier : att.value; - return bonus || 0; -} - -export function doCheckWork({bonus, advantage = 0}){ - let rolls = roll(2,20); - let chosenRoll; - if (advantage === 1){ - chosenRoll = Math.max.apply(rolls); - } else if (advantage === -1){ - chosenRoll = Math.min.apply(rolls); - } else { - chosenRoll = rolls[0]; - } - let result = chosenRoll + bonus; - return {rolls, bonus, chosenRoll, result}; -} - -export default doCheck; diff --git a/app/imports/api/engine/oldActions/getAncestorContext.js b/app/imports/api/engine/oldActions/getAncestorContext.js deleted file mode 100644 index 9508107b..00000000 --- a/app/imports/api/engine/oldActions/getAncestorContext.js +++ /dev/null @@ -1,15 +0,0 @@ -import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; - -export default function getAncestorContext(prop){ - // Build ancestor context - const actionContext = {}; - let ancestorIds = prop.ancestors.map(ref => ref.id); - CreatureProperties.find({ - _id: {$in: ancestorIds} - }, { - sort: {order: 1}, - }).forEach(ancestor => { - actionContext[`#${ancestor.type}`] = ancestor; - }); - return actionContext; -} diff --git a/app/imports/api/engine/oldActions/spendResources.js b/app/imports/api/engine/oldActions/spendResources.js deleted file mode 100644 index ef71c04a..00000000 --- a/app/imports/api/engine/oldActions/spendResources.js +++ /dev/null @@ -1,93 +0,0 @@ -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 spendResources({prop, log}){ - // Check Uses - if (prop.usesUsed >= prop.usesResult){ - throw new Meteor.Error('Insufficient Uses', - 'This prop has no uses left'); - } - // Resources - if (prop.insufficientResources){ - throw new Meteor.Error('Insufficient Resources', - 'This creature doesn\'t have sufficient resources to perform this prop'); - } - // Items - let itemQuantityAdjustments = []; - let spendLog = []; - let gainLog = []; - prop.resources.itemsConsumed.forEach(itemConsumed => { - if (!itemConsumed.itemId){ - throw new Meteor.Error('Ammo not selected', - 'No ammo was selected for this prop'); - } - let item = CreatureProperties.findOne(itemConsumed.itemId); - if (!item || item.ancestors[0].id !== prop.ancestors[0].id){ - throw new Meteor.Error('Ammo not found', - 'The prop\'s ammo was not found on the creature'); - } - if (!item.equipped){ - throw new Meteor.Error('Ammo not equipped', - '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); - } - }); - // 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 = CreatureProperties.findOne(attConsumed.statId); - 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/properties/subSchemas/ResourcesSchema.js b/app/imports/api/properties/subSchemas/ResourcesSchema.js deleted file mode 100644 index 4c36bbb2..00000000 --- a/app/imports/api/properties/subSchemas/ResourcesSchema.js +++ /dev/null @@ -1,68 +0,0 @@ -import SimpleSchema from 'simpl-schema'; -import { - ItemConsumedSchema, - ComputedOnlyItemConsumedSchema, - ComputedItemConsumedSchema -} from '/imports/api/properties/subSchemas/ItemConsumedSchema.js'; -import { - AttributeConsumedSchema, - ComputedOnlyAttributeConsumedSchema, - ComputedAttributeConsumedSchema -} from '/imports/api/properties/subSchemas/AttributeConsumedSchema.js'; - -const ResourcesSchema = new SimpleSchema({ - itemsConsumed: { - type: Array, - defaultValue: [], - }, - 'itemsConsumed.$': { - type: ItemConsumedSchema, - }, - attributesConsumed: { - type: Array, - defaultValue: [], - }, - 'attributesConsumed.$': { - type: AttributeConsumedSchema, - }, -}); - -const ResourcesComputedOnlySchema = new SimpleSchema({ - itemsConsumed: { - type: Array, - defaultValue: [], - }, - 'itemsConsumed.$': { - type: ComputedOnlyItemConsumedSchema, - }, - attributesConsumed: { - type: Array, - defaultValue: [], - }, - 'attributesConsumed.$': { - type: ComputedOnlyAttributeConsumedSchema, - }, -}); - -const ResourcesComputedSchema = new SimpleSchema({ - itemsConsumed: { - type: Array, - defaultValue: [], - }, - 'itemsConsumed.$': { - type: ComputedItemConsumedSchema, - }, - attributesConsumed: { - type: Array, - defaultValue: [], - }, - 'attributesConsumed.$': { - type: ComputedAttributeConsumedSchema, - }, -}); - -export { - ResourcesSchema, - ResourcesComputedOnlySchema, - ResourcesComputedSchema, -};