Fixed failing tests and action engine props

This commit is contained in:
Thaum Rystra
2024-02-22 09:11:00 +02:00
parent b41d26b3ad
commit 5141704e23
18 changed files with 181 additions and 140 deletions

View File

@@ -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',

View File

@@ -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;

View File

@@ -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';

View File

@@ -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,

View File

@@ -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!';

View File

@@ -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({

View File

@@ -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) {

View File

@@ -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<void> {
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);
}

View File

@@ -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<void> {
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);
}

View File

@@ -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<void> {
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);
}
}

View File

@@ -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<void> {
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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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<number>
// Other tasks promise nothing
export default async function applyTask(
action: EngineAction, task: PropTask | ItemAsAmmoTask, userInput: InputProvider
action: EngineAction, task: PropTask | ItemAsAmmoTask, inputProvider: InputProvider
): Promise<void>
export default async function applyTask(
action: EngineAction, task: Task, userInput: InputProvider
action: EngineAction, task: Task, inputProvider: InputProvider
): Promise<void | number> {
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);
}
}

View File

@@ -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');
});
});

View File

@@ -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}`);
}

View File

@@ -0,0 +1,5 @@
process.on('unhandledRejection', (error, p) => {
console.dir(error.stack);
console.error('Unhandled Rejection at:', p, 'reason:', error)
process.exit(1)
});