diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index eaa3fa85..e1a61f33 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -3,8 +3,6 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions'; -import { applyTriggers } from '/imports/api/engine/actions/applyTriggers'; -import ActionContext from '/imports/api/engine/actions/ActionContext'; const damageProperty = new ValidatedMethod({ name: 'creatureProperties.damage', diff --git a/app/imports/api/creature/creatures/CreatureVariables.ts b/app/imports/api/creature/creatures/CreatureVariables.ts index 8a307198..2578edfa 100644 --- a/app/imports/api/creature/creatures/CreatureVariables.ts +++ b/app/imports/api/creature/creatures/CreatureVariables.ts @@ -2,6 +2,9 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures'; import ParseNode from '/imports/parser/parseTree/ParseNode'; import array from '/imports/parser/parseTree/array'; import constant, { isFiniteNode } from '/imports/parser/parseTree/constant'; +import resolve from '/imports/parser/resolve'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; +import Context from '/imports/parser/types/Context'; //set up the collection for creature variables const CreatureVariables = new Mongo.Collection('creatureVariables'); @@ -45,6 +48,15 @@ export function getNumberFromScope(name, scope) { return parseNode.value; } +export async function getConstantValueFromScope( + name, scope +) { + const parseNode = getParseNodeFromScope(name, scope); + if (!parseNode) return; + if (parseNode.parseType !== 'constant') return; + return parseNode.value; +} + export function getParseNodeFromScope(name, scope): ParseNode | undefined { let value = getFromScope(name, scope); if (!value) return; diff --git a/app/imports/api/creature/creatures/methods/restCreature.js b/app/imports/api/creature/creatures/methods/restCreature.js index f281bbf2..e8768b33 100644 --- a/app/imports/api/creature/creatures/methods/restCreature.js +++ b/app/imports/api/creature/creatures/methods/restCreature.js @@ -4,8 +4,6 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions'; import { union } from 'lodash'; -import ActionContext from '/imports/api/engine/actions/ActionContext'; -import { applyTriggers } from '/imports/api/engine/actions/applyTriggers'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty'; import { getFilter } from '/imports/api/parenting/parentingFunctions'; diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts index b1829d66..a6785ef0 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts @@ -9,14 +9,6 @@ import { } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; import { Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult'; import Alea from 'alea'; -import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables'; -import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; - -process.on('unhandledRejection', (error, p) => { - console.dir(error.stack); - console.error('Unhandled Rejection at:', p, 'reason:', error) - process.exit(1) -}); const [ creatureId, targetCreatureId, targetCreature2Id, diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts index 67ddcb64..8934427e 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts @@ -102,7 +102,7 @@ async function applyAttackToTarget( const targetScope = getVariables(targetId); const targetArmor = getNumberFromScope('armor', targetScope) - if (Number.isFinite(targetArmor)) { + if (targetArmor !== undefined) { let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : result > targetArmor ? 'Hit!' : 'Miss!'; diff --git a/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts b/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts index 3c21c894..56d36a0d 100644 --- a/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts @@ -57,7 +57,7 @@ export default async function applyBuffProperty( //Log the buff let logValue = prop.description?.value if (prop.description?.text) { - recalculateInlineCalculations(prop.description, action, 'resolve', userInput); + recalculateInlineCalculations(prop.description, action, 'reduce', userInput); logValue = prop.description?.value; } result.appendLog({ diff --git a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts index ef6d48d0..f55272f5 100644 --- a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts @@ -1,6 +1,6 @@ import { some, includes, difference, intersection } from 'lodash'; -import { getParseNodeFromScope } from '/imports/api/creature/creatures/CreatureVariables'; +import { getConstantValueFromScope } from '/imports/api/creature/creatures/CreatureVariables'; import { EngineAction } from '/imports/api/engine/action/EngineActions'; import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope'; @@ -9,15 +9,16 @@ import { PropTask } from '/imports/api/engine/action/tasks/Task'; import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; import { isFiniteNode } from '/imports/parser/parseTree/constant'; import resolve from '/imports/parser/resolve'; -import Context from '../../../../parser/types/Context'; import toString from '/imports/parser/toString'; import { getPropertiesOfType } from '/imports/api/engine/loadCreatures'; import applyTask from '/imports/api/engine/action/tasks/applyTask'; import InputProvider from '/imports/api/engine/action/functions/InputProvider'; import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags'; +import Context from '/imports/parser/types/Context'; +import applySavingThrowProperty from '/imports/api/engine/action/applyProperties/applySavingThrowProperty'; export default async function applyDamageProperty( - task: PropTask, action: EngineAction, result: TaskResult, userInput + task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider ) { const prop = task.prop; const scope = getEffectiveActionScope(action); @@ -28,7 +29,7 @@ export default async function applyDamageProperty( // Choose target const damageTargets = prop.target === 'self' ? [action.creatureId] : task.targetIds; // Determine if the hit is critical - const criticalHit = getParseNodeFromScope('~criticalHit', scope)?.value + const criticalHit = await getConstantValueFromScope('~criticalHit', scope) && prop.damageType !== 'healing'; // Can't critically heal // Double the damage rolls if the hit is critical const context = new Context({ @@ -40,7 +41,7 @@ export default async function applyDamageProperty( const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage'; // roll the dice only and store that string - recalculateCalculation(prop.amount, action, 'compile', userInput); + recalculateCalculation(prop.amount, action, 'compile', inputProvider); const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context); if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); @@ -72,7 +73,7 @@ export default async function applyDamageProperty( typeof damage !== 'number' || !isFinite(damage) ) { - return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput); + return applyDefaultAfterPropTasks(action, prop, damageTargets, inputProvider); } // Round the damage to a whole number @@ -80,7 +81,7 @@ export default async function applyDamageProperty( scope['~damage'] = { value: damage }; // Convert extra damage into the stored type - const lastDamageType = getParseNodeFromScope('~lastDamageType')?.value; + const lastDamageType = await getConstantValueFromScope('~lastDamageType', scope); if (prop.damageType === 'extra' && typeof lastDamageType === 'string') { prop.damageType = lastDamageType; } @@ -95,10 +96,10 @@ export default async function applyDamageProperty( (prop.damageType !== 'healing' ? ' damage ' : ''); // If there is a save, calculate the save damage - let damageOnSave, saveNode, saveRoll; + let damageOnSave, saveProp, saveRoll; if (prop.save) { if (prop.save.damageFunction?.calculation) { - recalculateCalculation(prop.save.damageFunction, action, 'compile', userInput); + recalculateCalculation(prop.save.damageFunction, action, 'compile', inputProvider); context.errors = []; const { result: saveDamageRolled } = await resolve( 'roll', prop.save.damageFunction.valueNode, scope, context @@ -112,14 +113,14 @@ export default async function applyDamageProperty( if ( !isFiniteNode(saveDamageResult) ) { - return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput); + return applyDefaultAfterPropTasks(action, prop, damageTargets, inputProvider); } // Round the damage to a whole number damageOnSave = Math.floor(saveDamageResult.value); } else { damageOnSave = Math.floor(damage / 2); } - saveNode = { + saveProp = { node: { ...prop.save, name: prop.save.stat, @@ -136,8 +137,11 @@ export default async function applyDamageProperty( // If there is a saving throw, apply that first if (prop.save) { - await applySavingThrow(saveNode, actionContext); - if (getParseNodeFromScope('~saveSucceeded', scope)?.value) { + await applySavingThrowProperty({ + prop: saveProp, + targetIds: task.targetIds, + }, action, result, inputProvider); + if (await getConstantValueFromScope('~saveSucceeded', scope)) { // Log the total damage logValue.push(toString(reduced)); // Log the save damage @@ -165,14 +169,18 @@ export default async function applyDamageProperty( // Deal the damage to the target await dealDamage( - action, prop, result, userInput, target, prop.damageType, damageToApply + action, prop, result, inputProvider, target, prop.damageType, damageToApply ); } } else { // There are no targets, just log the result logValue.push(`**${damage}** ${suffix}`); if (prop.save) { - await applySavingThrow(saveNode, actionContext); + await applySavingThrowProperty(saveProp, action, result, inputProvider); + await applySavingThrowProperty({ + prop: saveProp, + targetIds: task.targetIds, + }, action, result, inputProvider); logValue.push(`**${damageOnSave}** ${suffix} on a successful save`); } } @@ -181,7 +189,7 @@ export default async function applyDamageProperty( value: logValue.join('\n'), inline: true, }, damageTargets); - return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput); + return applyDefaultAfterPropTasks(action, prop, damageTargets, inputProvider); } function damageFunctionText(save) { diff --git a/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts b/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts index bbb79cc7..d139c950 100644 --- a/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts @@ -1,18 +1,19 @@ import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; import recalculateInlineCalculations from '/imports/api/engine/action/functions/recalculateInlineCalculations'; import { PropTask } from '/imports/api/engine/action/tasks/Task'; import TaskResult, { LogContent } from '/imports/api/engine/action/tasks/TaskResult'; export default async function applyNoteProperty( - task: PropTask, action: EngineAction, result: TaskResult, userInput + task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider ): Promise { const prop = task.prop; let contents: LogContent[] | undefined = undefined; const logContent: LogContent = {}; if (prop.name) logContent.name = prop.name; if (prop.summary?.text) { - await recalculateInlineCalculations(prop.summary, action); + await recalculateInlineCalculations(prop.summary, action, 'reduce', inputProvider); logContent.value = prop.summary.value; } @@ -21,7 +22,7 @@ export default async function applyNoteProperty( } // Log description if (prop.description?.text) { - await recalculateInlineCalculations(prop.description, action); + await recalculateInlineCalculations(prop.description, action, 'reduce', inputProvider); if (!contents) contents = []; contents.push({ value: prop.description.value }); } @@ -31,5 +32,5 @@ export default async function applyNoteProperty( targetIds: task.targetIds, }); } - return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); + return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); } \ No newline at end of file diff --git a/app/imports/api/engine/action/applyProperties/applyRollProperty.ts b/app/imports/api/engine/action/applyProperties/applyRollProperty.ts index b683c454..ef1d2b2c 100644 --- a/app/imports/api/engine/action/applyProperties/applyRollProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyRollProperty.ts @@ -1,17 +1,19 @@ import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; import { rollAndReduceCalculation } from '/imports/api/engine/action/functions/recalculateCalculation'; import { PropTask } from '/imports/api/engine/action/tasks/Task'; import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; +import { isFiniteNode } from '/imports/parser/parseTree/constant'; import toString from '/imports/parser/toString'; -export default async function roll( - task: PropTask, action: EngineAction, result: TaskResult, userInput +export default async function applyRollProperty( + task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider ): Promise { const prop = task.prop; // If there isn't a calculation, just apply the children instead if (!prop.roll?.calculation) { - return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); + return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); } const logValue: string[] = []; @@ -19,7 +21,7 @@ export default async function roll( // roll the dice only and store that string const { rolled, reduced, errors - } = await rollAndReduceCalculation(prop.roll, action); + } = await rollAndReduceCalculation(prop.roll, action, inputProvider); if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); @@ -38,8 +40,8 @@ export default async function roll( } // If we didn't end up with a constant or a number of finite value, give up - if (reduced?.parseType !== 'constant' || (reduced.valueType === 'number' && !isFinite(reduced.value))) { - return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); + if (reduced?.parseType !== 'constant' || !isFiniteNode(reduced)) { + return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); } const value = reduced.value; @@ -54,5 +56,5 @@ export default async function roll( }, task.targetIds); // Apply children - return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); + return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); } diff --git a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts index 65e0a861..f6bbc9cb 100644 --- a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts @@ -1,57 +1,67 @@ -// TODO +import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables'; +import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; +import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; +import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope'; +import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation'; +import { applyUnresolvedEffects } from '/imports/api/engine/action/methods/doCheck'; +import { PropTask } from '/imports/api/engine/action/tasks/Task'; +import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; +import { getVariables } from '/imports/api/engine/loadCreatures'; +import numberToSignedString from '/imports/api/utility/numberToSignedString'; +import { isFiniteNode } from '/imports/parser/parseTree/constant'; -export default function applySavingThrow(node, actionContext) { - applyNodeTriggers(node, 'before', actionContext); - const prop = node.doc - const originalTargets = actionContext.targets; +export default async function applySavingThrowProperty( + task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider +): Promise { - let saveTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets; + const prop = task.prop; + const originalTargetIds = task.targetIds; - recalculateCalculation(prop.dc, actionContext); + const saveTargetIds = prop.target === 'self' ? [action.creatureId] : originalTargetIds; - const dc = (prop.dc?.value); - if (!isFinite(dc)) { - actionContext.addLog({ + if (saveTargetIds.length > 1) + + recalculateCalculation(prop.dc, action, 'reduce', inputProvider); + + if (!isFiniteNode(prop.dc)) { + result.appendLog({ name: 'Error', value: 'Saving throw requires a DC', - }); - return node.children.forEach(child => applyProperty(child, actionContext)); + }, saveTargetIds); + return applyDefaultAfterPropTasks(action, prop, saveTargetIds, inputProvider); } - if (!prop.silent) actionContext.addLog({ + + const dc = (prop.dc?.value); + if (!prop.silent) result.appendLog({ name: prop.name, value: `DC **${dc}**`, inline: true, - }); - const scope = actionContext.scope; + ...prop.silent && { silenced: prop.silent } + }, saveTargetIds); + const scope = await getEffectiveActionScope(action); // If there are no save targets, apply all children as if the save both - // succeeeded and failed - if (!saveTargets?.length) { - scope['~saveFailed'] = { value: true }; - scope['~saveSucceeded'] = { value: true }; - return applyChildren(node, actionContext); + // succeeded and failed + if (!saveTargetIds?.length) { + result.scope = { + ['~saveFailed']: { value: true }, + ['~saveSucceeded']: { value: true }, + } + return applyDefaultAfterPropTasks(action, prop, saveTargetIds, inputProvider); } // Each target makes the saving throw - saveTargets.forEach(target => { - delete scope['~saveFailed']; - delete scope['~saveSucceeded']; - delete scope['~saveDiceRoll']; - delete scope['~saveRoll']; + for (const targetId of saveTargetIds) { - const applyChildrenToTarget = function () { - actionContext.targets = [target]; - return applyChildren(node, actionContext); - }; - - const save = target.variables[prop.stat]; + const save = getFromScope('save', getVariables(targetId)); if (!save) { - actionContext.addLog({ + result.appendLog({ name: 'Saving throw error', value: 'No saving throw found: ' + prop.stat, - }); - return applyChildrenToTarget(); + }, [targetId]); + applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider); } let rollModifierText = numberToSignedString(save.value, true); @@ -60,9 +70,9 @@ export default function applySavingThrow(node, actionContext) { rollModifierText += effectString; rollModifier += effectBonus; - let value, values, resultPrefix; + let value, resultPrefix; if (save.advantage === 1) { - const [a, b] = rollDice(2, 20); + const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]); if (a >= b) { value = a; resultPrefix = `Advantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`; @@ -71,7 +81,7 @@ export default function applySavingThrow(node, actionContext) { resultPrefix = `Advantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`; } } else if (save.advantage === -1) { - const [a, b] = rollDice(2, 20); + const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]); if (a <= b) { value = a; resultPrefix = `Disadvantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`; @@ -80,26 +90,24 @@ export default function applySavingThrow(node, actionContext) { resultPrefix = `Disadvantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`; } } else { - values = rollDice(1, 20); - value = values[0]; + const [[rolledValue]] = await inputProvider.rollDice([{ number: 1, diceSize: 20 }]); + value = rolledValue; resultPrefix = `1d20 [ ${value} ] ${rollModifierText}` } scope['~saveDiceRoll'] = { value }; - const result = value + rollModifier || 0; - scope['~saveRoll'] = { value: result }; - const saveSuccess = result >= dc; + const resultValue = value + rollModifier || 0; + scope['~saveRoll'] = { value: resultValue }; + const saveSuccess = resultValue >= dc; if (saveSuccess) { scope['~saveSucceeded'] = { value: true }; } else { scope['~saveFailed'] = { value: true }; } - if (!prop.silent) actionContext.addLog({ + if (!prop.silent) result.appendLog({ name: saveSuccess ? 'Successful save' : 'Failed save', - value: resultPrefix + '\n**' + result + '**', + value: resultPrefix + '\n**' + resultValue + '**', inline: true, - }); - return applyChildrenToTarget(); - }); - // reset the targets after the save to each child - actionContext.targets = originalTargets; + }, [targetId]); + return applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider); + } } diff --git a/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts b/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts index b55120d4..ac2a6946 100644 --- a/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts @@ -1,10 +1,19 @@ -// TODO +import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; +import { applyAfterTasksSkipChildren, applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; +import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation'; +import { PropTask } from '/imports/api/engine/action/tasks/Task'; +import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; -export default function applyToggle(node, actionContext) { - applyNodeTriggers(node, 'before', actionContext); - const prop = node.doc - recalculateCalculation(prop.condition, actionContext); +export default async function applyToggle( + task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider +): Promise { + + const prop = task.prop; + await recalculateCalculation(prop.condition, action, 'reduce', inputProvider); if (prop.condition?.value) { - return applyChildren(node, actionContext); + return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); + } else { + return applyAfterTasksSkipChildren(action, prop, task.targetIds, inputProvider); } } diff --git a/app/imports/api/engine/action/functions/applyTaskGroups.ts b/app/imports/api/engine/action/functions/applyTaskGroups.ts index 2670453e..1d378b93 100644 --- a/app/imports/api/engine/action/functions/applyTaskGroups.ts +++ b/app/imports/api/engine/action/functions/applyTaskGroups.ts @@ -4,6 +4,7 @@ import { getPropertyChildren, getSingleProperty } from '/imports/api/engine/load import { EngineAction } from '/imports/api/engine/action/EngineActions'; import applyTask from '../tasks/applyTask'; import { PropTask } from '../tasks/Task'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; /** * Get all the child tasks of a given property @@ -13,11 +14,11 @@ import { PropTask } from '../tasks/Task'; * @returns */ export async function applyChildren( - action: EngineAction, prop, targetIds: string[], userInput + action: EngineAction, prop, targetIds: string[], inputProvider: InputProvider ) { const children = await getPropertyChildren(action.creatureId, prop); for (const childProp of children) { - await applyTask(action, { prop: childProp, targetIds }, userInput); + await applyTask(action, { prop: childProp, targetIds }, inputProvider); } } @@ -28,24 +29,24 @@ export async function applyChildren( * @returns */ export async function applyAfterChildrenTriggers( - action: EngineAction, prop, targetIds: string[], userInput + action: EngineAction, prop, targetIds: string[], inputProvider: InputProvider ) { if (!prop.triggerIds?.afterChildren) return; for (const triggerId of prop.triggerIds.afterChildren) { const trigger = await getSingleProperty(action.creatureId, triggerId); if (!trigger) continue; - await applyTask(action, { prop: trigger, targetIds }, userInput); + await applyTask(action, { prop: trigger, targetIds }, inputProvider); } } export async function applyAfterTriggers( - action: EngineAction, prop, targetIds: string[], userInput + action: EngineAction, prop, targetIds: string[], inputProvider: InputProvider ) { if (!prop.triggerIds?.after) return; for (const triggerId of prop.triggerIds.after) { const trigger = await getSingleProperty(action.creatureId, triggerId); if (!trigger) continue; - await applyTask(action, { prop: trigger, targetIds }, userInput); + await applyTask(action, { prop: trigger, targetIds }, inputProvider); } } @@ -60,11 +61,11 @@ export async function applyAfterTriggers( * @returns */ export async function applyDefaultAfterPropTasks( - action: EngineAction, prop, targetIds: string[], userInput + action: EngineAction, prop, targetIds: string[], inputProvider: InputProvider ) { - await applyAfterTriggers(action, prop, targetIds, userInput); - await applyChildren(action, prop, targetIds, userInput); - await applyAfterChildrenTriggers(action, prop, targetIds, userInput); + await applyAfterTriggers(action, prop, targetIds, inputProvider); + await applyChildren(action, prop, targetIds, inputProvider); + await applyAfterChildrenTriggers(action, prop, targetIds, inputProvider); } /** @@ -77,10 +78,10 @@ export async function applyDefaultAfterPropTasks( * @returns */ export async function applyAfterTasksSkipChildren( - action: EngineAction, prop, targetIds: string[], userInput + action: EngineAction, prop, targetIds: string[], inputProvider: InputProvider ) { - await applyAfterTriggers(action, prop, targetIds, userInput); - await applyAfterChildrenTriggers(action, prop, targetIds, userInput); + await applyAfterTriggers(action, prop, targetIds, inputProvider); + await applyAfterChildrenTriggers(action, prop, targetIds, inputProvider); } /** @@ -93,11 +94,11 @@ export async function applyAfterTasksSkipChildren( * @returns */ export async function applyAfterPropTasksForSingleChild( - action: EngineAction, prop, childProp, targetIds: string[], userInput + action: EngineAction, prop, childProp, targetIds: string[], inputProvider: InputProvider ) { - await applyAfterTriggers(action, prop, targetIds, userInput); - await applyTask(action, { prop: childProp, targetIds }, userInput); - await applyAfterChildrenTriggers(action, prop, targetIds, userInput); + await applyAfterTriggers(action, prop, targetIds, inputProvider); + await applyTask(action, { prop: childProp, targetIds }, inputProvider); + await applyAfterChildrenTriggers(action, prop, targetIds, inputProvider); } /** @@ -110,13 +111,13 @@ export async function applyAfterPropTasksForSingleChild( * @returns */ export async function applyAfterPropTasksForSomeChildren( - action: EngineAction, prop, children, targetIds: string[], userInput + action: EngineAction, prop, children, targetIds: string[], inputProvider: InputProvider ) { - await applyAfterTriggers(action, prop, targetIds, userInput); + await applyAfterTriggers(action, prop, targetIds, inputProvider); for (const childProp of children) { - await applyTask(action, { prop: childProp, targetIds }, userInput); + await applyTask(action, { prop: childProp, targetIds }, inputProvider); } - await applyAfterChildrenTriggers(action, prop, targetIds, userInput); + await applyAfterChildrenTriggers(action, prop, targetIds, inputProvider); } /** @@ -128,14 +129,14 @@ export async function applyAfterPropTasksForSomeChildren( * @returns */ export async function applyTriggers( - action: EngineAction, prop, targetIds: string[], triggerPath: string, userInput + action: EngineAction, prop, targetIds: string[], triggerPath: string, inputProvider: InputProvider ) { const triggerIds = get(prop?.triggers, triggerPath); if (!triggerIds) return; for (const triggerId of triggerIds) { const trigger = await getSingleProperty(action.creatureId, triggerId); if (!trigger) continue; - await applyTask(action, { prop: trigger, targetIds }, userInput); + await applyTask(action, { prop: trigger, targetIds }, inputProvider); } } @@ -146,7 +147,7 @@ export async function applyTriggers( * @returns Copies of the task, but with a single target each */ export async function applyTaskToEachTarget( - action: EngineAction, task: PropTask, targetIds: string[] = task.targetIds, userInput + action: EngineAction, task: PropTask, targetIds: string[] = task.targetIds, inputProvider: InputProvider ) { if (targetIds.length <= 1) throw 'Must have multiple targets to split a task'; // If there are targets, apply a new task to each target @@ -154,6 +155,6 @@ export async function applyTaskToEachTarget( await applyTask(action, { ...task, targetIds: [targetId] - }, userInput); + }, inputProvider); } } diff --git a/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts b/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts index 41230fe5..b1d9500b 100644 --- a/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts +++ b/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts @@ -1,7 +1,14 @@ import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations'; import recalculateCalculation from './recalculateCalculation' +import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField'; +import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import ResolveLevel from '/imports/parser/types/ResolveLevel'; +import InputProvider from '/imports/api/engine/action/functions/InputProvider'; -export default async function recalculateInlineCalculations(inlineCalcObj, action, parseLevel, userInput) { +export default async function recalculateInlineCalculations( + inlineCalcObj: InlineCalculation, action: EngineAction, + parseLevel: ResolveLevel, userInput: InputProvider +) { // Skip if there are no calculations if (!inlineCalcObj?.inlineCalculations?.length) return; // Recalculate each calculation with the current scope diff --git a/app/imports/api/engine/action/methods/doCheck.js b/app/imports/api/engine/action/methods/doCheck.js index 02eb9d8e..3d60d1e6 100644 --- a/app/imports/api/engine/action/methods/doCheck.js +++ b/app/imports/api/engine/action/methods/doCheck.js @@ -5,9 +5,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions'; import rollDice from '/imports/parser/rollDice'; import numberToSignedString from '/imports/api/utility/numberToSignedString'; -import { applyTriggers } from '/imports/api/engine/actions/applyTriggers'; -import ActionContext from '/imports/api/engine/actions/ActionContext'; -import recalculateCalculation from '../../actions/applyPropertyByType/shared/recalculateCalculation'; import { getSingleProperty } from '/imports/api/engine/loadCreatures'; // TODO Migrate this to the new action engine diff --git a/app/imports/api/engine/action/tasks/applyTask.ts b/app/imports/api/engine/action/tasks/applyTask.ts index 4fb5e766..a33f9e76 100644 --- a/app/imports/api/engine/action/tasks/applyTask.ts +++ b/app/imports/api/engine/action/tasks/applyTask.ts @@ -9,16 +9,16 @@ import InputProvider from '/imports/api/engine/action/functions/InputProvider'; // DamagePropTask promises a number of actual damage done export default async function applyTask( - action: EngineAction, task: DamagePropTask, userInput: InputProvider + action: EngineAction, task: DamagePropTask, inputProvider: InputProvider ): Promise // Other tasks promise nothing export default async function applyTask( - action: EngineAction, task: PropTask | ItemAsAmmoTask, userInput: InputProvider + action: EngineAction, task: PropTask | ItemAsAmmoTask, inputProvider: InputProvider ): Promise export default async function applyTask( - action: EngineAction, task: Task, userInput: InputProvider + action: EngineAction, task: Task, inputProvider: InputProvider ): Promise { action.taskCount += 1; if (action.taskCount > 100) throw 'Only 100 properties can be applied at once'; @@ -28,9 +28,9 @@ export default async function applyTask( action.results.push(result); switch (task.subtaskFn) { case 'damageProp': - return applyDamagePropTask(task, action, result, userInput); + return applyDamagePropTask(task, action, result, inputProvider); case 'consumeItemAsAmmo': - return applyItemAsAmmoTask(task, action, result, userInput); + return applyItemAsAmmoTask(task, action, result, inputProvider); } } else { // Get property @@ -47,7 +47,7 @@ export default async function applyTask( for (const triggerId of prop.triggerIds.before) { const trigger = await getSingleProperty(action.creatureId, triggerId); if (!trigger) continue; - await applyTask(action, { prop: trigger, targetIds: task.targetIds }, userInput); + await applyTask(action, { prop: trigger, targetIds: task.targetIds }, inputProvider); } } @@ -57,6 +57,6 @@ export default async function applyTask( action.results.push(result); // Apply the property - return applyProperties[prop.type]?.(task, action, result, userInput); + return applyProperties[prop.type]?.(task, action, result, inputProvider); } } \ No newline at end of file diff --git a/app/imports/parser/parseTree/call.test.ts b/app/imports/parser/parseTree/call.test.ts index 27e8128c..32f01bed 100644 --- a/app/imports/parser/parseTree/call.test.ts +++ b/app/imports/parser/parseTree/call.test.ts @@ -11,4 +11,10 @@ describe('Call Node', function () { assert.isEmpty(context.errors) assert.equal(toString(result), 'min(unknownVariable, 3, 3d30)'); }); + it('reduces', async function () { + const callNode = parse('min( unknownVariable, 1 + 2, 3d30 )'); + const { result, context } = await resolve('reduce', callNode, undefined, undefined, inputProviderForTests); + assert.isEmpty(context.errors) + assert.equal(toString(result), '0'); + }); }); diff --git a/app/imports/parser/parseTree/call.ts b/app/imports/parser/parseTree/call.ts index ee628224..17fbe058 100644 --- a/app/imports/parser/parseTree/call.ts +++ b/app/imports/parser/parseTree/call.ts @@ -173,16 +173,13 @@ const call: CallFactory = { expectedType = argumentsExpected[index]; } if (expectedType === 'parseNode') return; - if ( - node.parseType !== expectedType - || ( - node.parseType === 'constant' - && node.valueType !== expectedType - ) - ) failed = true; + failed = !( + node.parseType === expectedType + || (node.parseType === 'constant' && node.valueType === expectedType) + ); if (failed && fn === 'reduce') { const typeName = typeof expectedType === 'string' ? expectedType : expectedType.constructor.name; - const nodeName = node.parseType; + const nodeName = node.parseType === 'constant' ? node.valueType : node.parseType; context.error(`Incorrect arguments to ${callNode.functionName} function` + `expected ${typeName} got ${nodeName}`); } diff --git a/app/imports/server/config/throwUnresolvedPromises.test.ts b/app/imports/server/config/throwUnresolvedPromises.test.ts new file mode 100644 index 00000000..68443fc6 --- /dev/null +++ b/app/imports/server/config/throwUnresolvedPromises.test.ts @@ -0,0 +1,5 @@ +process.on('unhandledRejection', (error, p) => { + console.dir(error.stack); + console.error('Unhandled Rejection at:', p, 'reason:', error) + process.exit(1) +}); \ No newline at end of file