Typescript all the parser things
This commit is contained in:
@@ -9,6 +9,14 @@ 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,
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function applyActionProperty(
|
||||
//Log the name and summary, check that the property has enough resources to fire
|
||||
const content: LogContent = { name: getPropertyTitle(prop) };
|
||||
if (prop.summary?.text) {
|
||||
await recalculateInlineCalculations(prop.summary, action);
|
||||
await recalculateInlineCalculations(prop.summary, action, 'reduce', userInput);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
if (prop.silent) content.silenced = true;
|
||||
@@ -188,7 +188,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
const rollModifierText = numberToSignedString(attack.value, true);
|
||||
let value, resultPrefix;
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
const [[a, b]] = await userInput.rollDice(attack, [{ number: 2, diceSize: 20 }]);
|
||||
const [[a, b]] = await userInput.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
@@ -197,7 +197,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
const [[a, b]] = await userInput.rollDice(attack, [{ number: 2, diceSize: 20 }]);
|
||||
const [[a, b]] = await userInput.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
@@ -206,7 +206,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else {
|
||||
[[value]] = await userInput.rollDice(attack, [{ number: 1, diceSize: 20 }]);
|
||||
[[value]] = await userInput.rollDice([{ number: 1, diceSize: 20 }]);
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
||||
}
|
||||
resultPushScope['~attackDiceRoll'] = { value };
|
||||
@@ -218,10 +218,12 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
|
||||
function applyCrits(value, scope, resultPushScope) {
|
||||
const scopeCritTarget = getNumberFromScope('~criticalHitTarget', scope);
|
||||
const criticalHitTarget = Number.isFinite(scopeCritTarget) ? scopeCritTarget : 20;
|
||||
const criticalHitTarget = scopeCritTarget !== undefined &&
|
||||
Number.isFinite(scopeCritTarget) ? scopeCritTarget : 20;
|
||||
|
||||
const scopeCritMissTarget = getNumberFromScope('~criticalMissTarget', scope);
|
||||
const criticalMissTarget = Number.isFinite(scopeCritMissTarget) ? scopeCritMissTarget : 1;
|
||||
const criticalMissTarget = scopeCritMissTarget !== undefined &&
|
||||
Number.isFinite(scopeCritMissTarget) ? scopeCritMissTarget : 1;
|
||||
|
||||
const criticalHit = value >= criticalHitTarget;
|
||||
const criticalMiss = value <= criticalMissTarget;
|
||||
|
||||
@@ -115,7 +115,7 @@ export default async function applyBranchProperty(
|
||||
let choices: string[];
|
||||
let chosenChildren: typeof children = [];
|
||||
if (children.length) {
|
||||
choices = await userInput.choose(action, children);
|
||||
choices = await userInput.choose(children);
|
||||
chosenChildren = filter(children, child => choices.includes(child._id));
|
||||
}
|
||||
if (!children.length || !chosenChildren.length) {
|
||||
|
||||
@@ -3,9 +3,11 @@ import { get } from 'lodash';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import { getPropertyDescendants } from '/imports/api/engine/loadCreatures';
|
||||
import resolve, { toString, map } from '/imports/parser/resolve';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import map from '/imports/parser/map';
|
||||
import toString from '/imports/parser/toString';
|
||||
import computedSchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey';
|
||||
import applyFnToKey, { applyFnToKeyAsync } from '/imports/api/engine/computation/utility/applyFnToKey';
|
||||
import accessor from '/imports/parser/parseTree/accessor';
|
||||
import TaskResult, { Mutation } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
@@ -55,7 +57,7 @@ export default async function applyBuffProperty(
|
||||
//Log the buff
|
||||
let logValue = prop.description?.value
|
||||
if (prop.description?.text) {
|
||||
recalculateInlineCalculations(prop.description, action);
|
||||
recalculateInlineCalculations(prop.description, action, 'resolve', userInput);
|
||||
logValue = prop.description?.value;
|
||||
}
|
||||
result.appendLog({
|
||||
@@ -83,17 +85,17 @@ async function crystalizeVariables(
|
||||
action: EngineAction, propList: any[], task: PropTask, result: TaskResult
|
||||
) {
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
propList.forEach(prop => {
|
||||
for (const prop of propList) {
|
||||
if (prop._skipCrystalize) {
|
||||
delete prop._skipCrystalize;
|
||||
return;
|
||||
}
|
||||
// Iterate through all the calculations and crystalize them
|
||||
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
for (const calcKey of computedSchemas[prop.type].computedFields()) {
|
||||
await applyFnToKeyAsync(prop, calcKey, async (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj?.parseNode) return;
|
||||
calcObj.parseNode = map(calcObj.parseNode, node => {
|
||||
calcObj.parseNode = await map(calcObj.parseNode, async node => {
|
||||
// Skip nodes that aren't symbols or accessors
|
||||
if (
|
||||
node.parseType !== 'accessor'
|
||||
@@ -117,22 +119,17 @@ async function crystalizeVariables(
|
||||
return node;
|
||||
} else {
|
||||
// Resolve all other variables
|
||||
const { result, context } = resolve('reduce', node, scope);
|
||||
context.errors?.forEach(error => {
|
||||
result.appendLog({
|
||||
name: 'Error',
|
||||
value: error,
|
||||
}, task.targetIds);
|
||||
});
|
||||
return result;
|
||||
const { result: nodeResult, context } = await resolve('reduce', node, scope);
|
||||
result.appendParserContextErrors(context, task.targetIds);
|
||||
return nodeResult;
|
||||
}
|
||||
});
|
||||
calcObj.calculation = toString(calcObj.parseNode);
|
||||
calcObj.hash = cyrb53(calcObj.calculation);
|
||||
});
|
||||
});
|
||||
}
|
||||
// For each key in the schema
|
||||
computedSchemas[prop.type].inlineCalculationFields().forEach(calcKey => {
|
||||
for (const calcKey of computedSchemas[prop.type].inlineCalculationFields()) {
|
||||
// That ends in .inlineCalculations
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const inlineCalcObj = get(prop, key);
|
||||
@@ -161,6 +158,6 @@ async function crystalizeVariables(
|
||||
}
|
||||
inlineCalcObj.hash = inlineCalcHash;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,88 @@
|
||||
// TODO
|
||||
import { some, includes, difference, intersection } from 'lodash';
|
||||
|
||||
export default function applyDamage(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
import { getParseNodeFromScope } 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';
|
||||
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';
|
||||
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';
|
||||
|
||||
const prop = node.doc
|
||||
const scope = actionContext.scope;
|
||||
export default async function applyDamageProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
) {
|
||||
const prop = task.prop;
|
||||
const scope = getEffectiveActionScope(action);
|
||||
|
||||
// Skip if there is no parse node to work with
|
||||
if (!prop.amount?.parseNode) return;
|
||||
|
||||
// Choose target
|
||||
let damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||
const damageTargets = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
// Determine if the hit is critical
|
||||
let criticalHit = scope['~criticalHit']?.value &&
|
||||
prop.damageType !== 'healing' // Can't critically heal
|
||||
;
|
||||
const criticalHit = getParseNodeFromScope('~criticalHit', scope)?.value
|
||||
&& prop.damageType !== 'healing'; // Can't critically heal
|
||||
// Double the damage rolls if the hit is critical
|
||||
let context = new Context({
|
||||
const context = new Context({
|
||||
options: { doubleRolls: criticalHit },
|
||||
});
|
||||
|
||||
// Gather all the lines we need to log into an array
|
||||
const logValue = [];
|
||||
const logValue: string[] = [];
|
||||
const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||
|
||||
// roll the dice only and store that string
|
||||
recalculateCalculation(prop.amount, actionContext, 'compile');
|
||||
const { result: rolled } = resolve('roll', prop.amount.valueNode, scope, context);
|
||||
recalculateCalculation(prop.amount, action, 'compile', userInput);
|
||||
const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context);
|
||||
if (rolled.parseType !== 'constant') {
|
||||
logValue.push(toString(rolled));
|
||||
}
|
||||
logErrors(context.errors, actionContext);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
|
||||
// Reset the errors so we don't log the same errors twice
|
||||
context.errors = [];
|
||||
|
||||
// Resolve the roll to a final value
|
||||
const { result: reduced } = resolve('reduce', rolled, scope, context);
|
||||
logErrors(context.errors, actionContext);
|
||||
const { result: reduced } = await resolve('reduce', rolled, scope, context);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
|
||||
// Store the result
|
||||
let damage: number | undefined = undefined;
|
||||
if (reduced.parseType === 'constant') {
|
||||
prop.amount.value = reduced.value;
|
||||
if (typeof reduced.value === 'number') {
|
||||
damage = reduced.value;
|
||||
}
|
||||
} else if (reduced.parseType === 'error') {
|
||||
prop.amount.value = null;
|
||||
} else {
|
||||
prop.amount.value = toString(reduced);
|
||||
}
|
||||
let damage = +reduced.value;
|
||||
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||
return applyChildren(node, actionContext);
|
||||
// If we didn't end up with damage of finite amount, give up
|
||||
if (
|
||||
typeof damage !== 'number'
|
||||
|| !isFinite(damage)
|
||||
) {
|
||||
return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput);
|
||||
}
|
||||
|
||||
// Round the damage to a whole number
|
||||
damage = Math.floor(damage);
|
||||
scope['~damage'] = damage;
|
||||
scope['~damage'] = { value: damage };
|
||||
|
||||
// Convert extra damage into the stored type
|
||||
if (prop.damageType === 'extra' && scope['~lastDamageType']?.value) {
|
||||
prop.damageType = scope['~lastDamageType']?.value;
|
||||
const lastDamageType = getParseNodeFromScope('~lastDamageType')?.value;
|
||||
if (prop.damageType === 'extra' && typeof lastDamageType === 'string') {
|
||||
prop.damageType = lastDamageType;
|
||||
}
|
||||
// Store current damage type
|
||||
if (prop.damageType !== 'healing') {
|
||||
@@ -68,7 +90,7 @@ export default function applyDamage(node, actionContext) {
|
||||
}
|
||||
|
||||
// Memoise the damage suffix for the log
|
||||
let suffix = (criticalHit ? ' critical ' : ' ') +
|
||||
const suffix = (criticalHit ? ' critical ' : ' ') +
|
||||
prop.damageType +
|
||||
(prop.damageType !== 'healing' ? ' damage ' : '');
|
||||
|
||||
@@ -76,17 +98,24 @@ export default function applyDamage(node, actionContext) {
|
||||
let damageOnSave, saveNode, saveRoll;
|
||||
if (prop.save) {
|
||||
if (prop.save.damageFunction?.calculation) {
|
||||
recalculateCalculation(prop.save.damageFunction, actionContext, undefined, 'compile');
|
||||
let { result: saveDamageRolled } = resolve('roll', prop.save.damageFunction.valueNode, scope, context);
|
||||
recalculateCalculation(prop.save.damageFunction, action, 'compile', userInput);
|
||||
context.errors = [];
|
||||
const { result: saveDamageRolled } = await resolve(
|
||||
'roll', prop.save.damageFunction.valueNode, scope, context
|
||||
);
|
||||
saveRoll = toString(saveDamageRolled);
|
||||
let { result: saveDamageResult } = resolve('reduce', saveDamageRolled, scope, context);
|
||||
const { result: saveDamageResult } = await resolve(
|
||||
'reduce', saveDamageRolled, scope, context
|
||||
);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||
return applyChildren(node, actionContext);
|
||||
if (
|
||||
!isFiniteNode(saveDamageResult)
|
||||
) {
|
||||
return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput);
|
||||
}
|
||||
damageOnSave = +saveDamageResult.value;
|
||||
// Round the damage to a whole number
|
||||
damageOnSave = Math.floor(damageOnSave);
|
||||
damageOnSave = Math.floor(saveDamageResult.value);
|
||||
} else {
|
||||
damageOnSave = Math.floor(damage / 2);
|
||||
}
|
||||
@@ -102,14 +131,13 @@ export default function applyDamage(node, actionContext) {
|
||||
|
||||
if (damageTargets && damageTargets.length) {
|
||||
// Iterate through all the targets
|
||||
damageTargets.forEach(target => {
|
||||
actionContext.target = [target];
|
||||
let damageToApply = damage;
|
||||
for (const target of damageTargets) {
|
||||
let damageToApply = damage || 0;
|
||||
|
||||
// If there is a saving throw, apply that first
|
||||
if (prop.save) {
|
||||
applySavingThrow(saveNode, actionContext);
|
||||
if (scope['~saveSucceeded']?.value) {
|
||||
await applySavingThrow(saveNode, actionContext);
|
||||
if (getParseNodeFromScope('~saveSucceeded', scope)?.value) {
|
||||
// Log the total damage
|
||||
logValue.push(toString(reduced));
|
||||
// Log the save damage
|
||||
@@ -136,49 +164,28 @@ export default function applyDamage(node, actionContext) {
|
||||
});
|
||||
|
||||
// Deal the damage to the target
|
||||
let damageDealt = dealDamage({
|
||||
target,
|
||||
damageType: prop.damageType,
|
||||
amount: damageToApply,
|
||||
actionContext
|
||||
});
|
||||
|
||||
// Log the damage done
|
||||
if (target._id === actionContext.creature._id) {
|
||||
// Target is same as self, log damage as such
|
||||
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
||||
} else {
|
||||
logValue.push(`Dealt **${damageDealt}** ${suffix} ${target.name && ' to '}${target.name}`);
|
||||
// Log the damage received on that creature's log as well
|
||||
insertCreatureLog.call({
|
||||
log: {
|
||||
creatureId: target._id,
|
||||
content: [{
|
||||
name,
|
||||
value: `Received **${damageDealt}** ${suffix}`,
|
||||
}],
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
await dealDamage(
|
||||
action, prop, result, userInput, target, prop.damageType, damageToApply
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// There are no targets, just log the result
|
||||
logValue.push(`**${damage}** ${suffix}`);
|
||||
if (prop.save) {
|
||||
applySavingThrow(saveNode, actionContext);
|
||||
await applySavingThrow(saveNode, actionContext);
|
||||
logValue.push(`**${damageOnSave}** ${suffix} on a successful save`);
|
||||
}
|
||||
}
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
if (logValue.length) result.appendLog({
|
||||
name: logName,
|
||||
value: logValue.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
return applyChildren(node, actionContext);
|
||||
}, damageTargets);
|
||||
return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput);
|
||||
}
|
||||
|
||||
function damageFunctionText(save, scope, context, actionContext) {
|
||||
if (!save) return [];
|
||||
function damageFunctionText(save) {
|
||||
if (!save) return;
|
||||
if (!save.damageFunction) {
|
||||
return '**Half damage on successful save**';
|
||||
}
|
||||
@@ -239,9 +246,12 @@ function multiplierAppliesTo(damageProp, multiplierType) {
|
||||
}
|
||||
}
|
||||
|
||||
function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
async function dealDamage(
|
||||
action: EngineAction, prop: any, result: TaskResult, userInput: InputProvider,
|
||||
targetId: string, damageType: string, amount: number
|
||||
) {
|
||||
// Get all the health bars and do damage to them
|
||||
let healthBars = getPropertiesOfType(target._id, 'attribute');
|
||||
let healthBars = getPropertiesOfType(targetId, 'attribute');
|
||||
|
||||
// Keep only the healthbars that can take damage/healing
|
||||
healthBars = healthBars.filter((bar) => {
|
||||
@@ -276,24 +286,20 @@ function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
const totalDamage = amount;
|
||||
let damageLeft = totalDamage;
|
||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||
healthBars.forEach(healthBar => {
|
||||
for (const healthBar of healthBars) {
|
||||
if (damageLeft === 0) return;
|
||||
// Replace the healthbar by the one in the action context if we can
|
||||
// The damagePropertyWork function bashes the prop with the damage
|
||||
// So we can use the new value in later action properties
|
||||
if (healthBar.variableName) {
|
||||
const targetHealthBar = target.variables[healthBar.variableName];
|
||||
if (targetHealthBar?._id === healthBar._id) {
|
||||
healthBar = targetHealthBar;
|
||||
}
|
||||
}
|
||||
// Do the damage
|
||||
let damageAdded = damagePropertyWork({
|
||||
prop: healthBar,
|
||||
operation: 'increment',
|
||||
value: damageLeft,
|
||||
actionContext
|
||||
});
|
||||
const damageAdded = await applyTask(action, {
|
||||
prop,
|
||||
targetIds: [targetId],
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
operation: 'increment',
|
||||
value: +damageLeft || 0,
|
||||
targetProp: healthBar,
|
||||
},
|
||||
}, userInput);
|
||||
|
||||
damageLeft -= damageAdded;
|
||||
// Prevent overflow
|
||||
if (
|
||||
@@ -303,6 +309,6 @@ function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
) {
|
||||
damageLeft = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions
|
||||
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 { toString } from '/imports/parser/resolve';
|
||||
import toString from '/imports/parser/toString';
|
||||
|
||||
export default async function roll(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
|
||||
type InputProvider = {
|
||||
rollDice(
|
||||
action: EngineAction, dice: { number: number, diceSize: number }[]
|
||||
dice: { number: number, diceSize: number }[]
|
||||
): Promise<number[][]>;
|
||||
/**
|
||||
* Choose from a provided selection
|
||||
@@ -11,7 +9,6 @@ type InputProvider = {
|
||||
* @param quantity Number of choices to make [min, max] inclusive, where -1 means no limit
|
||||
*/
|
||||
choose(
|
||||
action: EngineAction,
|
||||
choices: ({ _id: string } & Record<string, any>)[],
|
||||
quantity?: [min: number, max: number],
|
||||
): Promise<string[]>;
|
||||
|
||||
@@ -6,7 +6,7 @@ const inputProviderForTests: InputProvider = {
|
||||
* rollDice function returns the average roll for every dice rolled
|
||||
* [5d10, 1d4] => [[6,6,6,6,6], [3]]
|
||||
*/
|
||||
async rollDice(action, dice) {
|
||||
async rollDice(dice = []) {
|
||||
const result: number[][] = [];
|
||||
for (const diceRoll of dice) {
|
||||
const averageRoll = Math.round(diceRoll.diceSize / 2);
|
||||
@@ -21,7 +21,7 @@ const inputProviderForTests: InputProvider = {
|
||||
/**
|
||||
* For testing, always return the minimum number of choices, always choosing the first options
|
||||
*/
|
||||
async choose(action, choices, quantity = [1, 1]) {
|
||||
async choose(choices, quantity = [1, 1]) {
|
||||
const chosen: string[] = [];
|
||||
const choiceQuantity = quantity[0] <= 0 ? 1 : quantity[0];
|
||||
for (let i = 0; i < choiceQuantity && i < choices.length; i += 1) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Context, toPrimitiveOrString } from '/imports/parser/resolve';
|
||||
import Context from '../../../../parser/types/Context';
|
||||
import toPrimitiveOrString from '/imports/parser/toPrimitiveOrString';
|
||||
import {
|
||||
aggregateCalculationEffects,
|
||||
aggregateCalculationProficiencies,
|
||||
@@ -26,7 +27,7 @@ export default async function recalculateCalculation(
|
||||
const {
|
||||
result: unaffectedResult,
|
||||
context
|
||||
} = resolve(parseLevel, calcObj.parseNode, scope);
|
||||
} = await resolve(parseLevel, calcObj.parseNode, scope);
|
||||
calcObj.valueNode = unaffectedResult;
|
||||
|
||||
// store the unaffected value
|
||||
@@ -47,7 +48,7 @@ export default async function recalculateCalculation(
|
||||
// Resolve the modified valueNode, use the same context
|
||||
const {
|
||||
result: finalResult
|
||||
} = resolve(parseLevel, calcObj.parseNode, scope, context);
|
||||
} = await resolve(parseLevel, calcObj.parseNode, scope, context);
|
||||
|
||||
// Store the errors
|
||||
calcObj.errors = context.errors;
|
||||
@@ -55,12 +56,12 @@ export default async function recalculateCalculation(
|
||||
// Store the value and its primitive
|
||||
calcObj.value = toPrimitiveOrString(finalResult);
|
||||
calcObj.valueNode = finalResult;
|
||||
|
||||
}
|
||||
|
||||
export async function rollAndReduceCalculation(
|
||||
calcObj: CalculatedField, action: EngineAction, userInput: InputProvider
|
||||
) {
|
||||
if (!calcObj) throw new Error('calcObj is required');
|
||||
const context = new Context();
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
// Compile
|
||||
@@ -68,10 +69,10 @@ export async function rollAndReduceCalculation(
|
||||
const compiled = calcObj.valueNode;
|
||||
|
||||
// Roll
|
||||
const { result: rolled } = resolve('roll', calcObj.valueNode, scope, context);
|
||||
const { result: rolled } = await resolve('roll', calcObj.valueNode, scope, context, userInput);
|
||||
|
||||
// Reduce
|
||||
const { result: reduced } = resolve('reduce', rolled, scope, context);
|
||||
const { result: reduced } = await resolve('reduce', rolled, scope, context, userInput);
|
||||
|
||||
// Return
|
||||
return { compiled, rolled, reduced, errors: context.errors };
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations';
|
||||
import recalculateCalculation from './recalculateCalculation'
|
||||
|
||||
export default async function recalculateInlineCalculations(inlineCalcObj, action) {
|
||||
export default async function recalculateInlineCalculations(inlineCalcObj, action, parseLevel, userInput) {
|
||||
// Skip if there are no calculations
|
||||
if (!inlineCalcObj?.inlineCalculations?.length) return;
|
||||
// Recalculate each calculation with the current scope
|
||||
const promises = [];
|
||||
for (const calc of inlineCalcObj.inlineCalculations) {
|
||||
promises.push(recalculateCalculation(calc, action));
|
||||
await recalculateCalculation(calc, action, undefined, userInput);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
// Embed the new calculated values
|
||||
embedInlineCalculations(inlineCalcObj);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
@@ -31,7 +32,7 @@ export default async function spendResources(
|
||||
for (const att of prop.resources.attributesConsumed) {
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
const statToDamage = await getFromScope(att.variableName, scope);
|
||||
await recalculateCalculation(att.quantity, action, 'reduce');
|
||||
await recalculateCalculation(att.quantity, action, 'reduce', userInput);
|
||||
await applyTask(action, {
|
||||
prop,
|
||||
targetIds: [action.creatureId],
|
||||
@@ -48,7 +49,7 @@ export default async function spendResources(
|
||||
// Iterate through all the items consumed and consume them
|
||||
if (prop.resources?.itemsConsumed?.length) {
|
||||
for (const itemConsumed of prop.resources.itemsConsumed) {
|
||||
await recalculateCalculation(itemConsumed.quantity, action, 'reduce');
|
||||
await recalculateCalculation(itemConsumed.quantity, action, 'reduce', userInput);
|
||||
if (!itemConsumed.itemId) {
|
||||
throw 'No ammo was selected';
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Context from '../../../../parser/types/Context';
|
||||
|
||||
/**
|
||||
* The result of running a task containing all the changes that need to be made to the listed
|
||||
* targets
|
||||
@@ -36,6 +38,22 @@ export default class TaskResult {
|
||||
}
|
||||
latestMutation.contents.push(content);
|
||||
}
|
||||
appendParserContextErrors(context: Context, targetIds) {
|
||||
if (!context.errors?.length) return;
|
||||
if (!this.mutations.length) {
|
||||
this.mutations.push({ targetIds, contents: [] });
|
||||
}
|
||||
const latestMutation = this.mutations[this.mutations.length - 1]
|
||||
if (!latestMutation.contents) {
|
||||
latestMutation.contents = [];
|
||||
}
|
||||
context.errors?.forEach(error => {
|
||||
latestMutation.contents?.push({
|
||||
name: 'Error',
|
||||
value: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type Mutation = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
|
||||
export default async function applyDamagePropTask(
|
||||
task: DamagePropTask, action: EngineAction, result: TaskResult, userInput
|
||||
): Promise<void> {
|
||||
): Promise<number> {
|
||||
const prop = task.prop;
|
||||
|
||||
if (task.targetIds.length > 1) {
|
||||
@@ -74,7 +74,7 @@ export default async function applyDamagePropTask(
|
||||
let damage, newValue, increment;
|
||||
targetProp = await getSingleProperty(targetId, targetPropId);
|
||||
|
||||
if (!targetProp) return;
|
||||
if (!targetProp) return value;
|
||||
|
||||
if (operation === 'set') {
|
||||
const total = targetProp.total || 0;
|
||||
@@ -128,4 +128,5 @@ export default async function applyDamagePropTask(
|
||||
});
|
||||
}
|
||||
await applyTriggers(action, prop, [action.creatureId], 'damageProperty.after', userInput);
|
||||
return increment;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import Task from './Task';
|
||||
import Task, { DamagePropTask, ItemAsAmmoTask, PropTask } from './Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import applyDamagePropTask from '/imports/api/engine/action/tasks/applyDamagePropTask';
|
||||
import applyItemAsAmmoTask from '/imports/api/engine/action/tasks/applyItemAsAmmoTask';
|
||||
@@ -7,9 +7,19 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import applyProperties from '/imports/api/engine/action/applyProperties';
|
||||
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
|
||||
): Promise<number>
|
||||
|
||||
// Other tasks promise nothing
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: PropTask | ItemAsAmmoTask, userInput: InputProvider
|
||||
): Promise<void>
|
||||
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: Task, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
): Promise<void | number> {
|
||||
action.taskCount += 1;
|
||||
if (action.taskCount > 100) throw 'Only 100 properties can be applied at once';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { traverse } from '/imports/parser/resolve';
|
||||
import traverse from '/imports/parser/traverse';
|
||||
|
||||
export default function linkCalculationDependencies(dependencyGraph, prop, { propsById }) {
|
||||
prop._computationDetails.calculations.forEach(calcObj => {
|
||||
|
||||
@@ -125,6 +125,6 @@ function parseCalculation(calcObj) {
|
||||
message: prettifyParseError(e),
|
||||
};
|
||||
calcObj.parseError = error;
|
||||
calcObj.parseNode = errorNode.create({ error });
|
||||
calcObj.parseNode = errorNode.create({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export default function computeAction(computation, node) {
|
||||
import CreatureComputation from '/imports/api/engine/computation/CreatureComputation';
|
||||
import { Node } from 'ngraph.graph';
|
||||
|
||||
export default function computeAction(computation: CreatureComputation, node: Node) {
|
||||
const prop = node.data;
|
||||
if (Number.isFinite(prop.uses?.value)) {
|
||||
prop.usesLeft = prop.uses.value - (prop.usesUsed || 0);
|
||||
@@ -2,27 +2,30 @@ import call from '/imports/parser/parseTree/call';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import operator from '/imports/parser/parseTree/operator';
|
||||
import parenthesis from '/imports/parser/parseTree/parenthesis';
|
||||
import resolve, { toPrimitiveOrString } from '/imports/parser/resolve';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import toPrimitiveOrString from '/imports/parser/toPrimitiveOrString';
|
||||
|
||||
export default function computeCalculation(computation, node) {
|
||||
export default async function computeCalculation(computation, node) {
|
||||
const calcObj = node.data;
|
||||
if (!calcObj) return;
|
||||
// resolve the parse node into the initial value
|
||||
resolveCalculationNode(calcObj, calcObj.parseNode, computation.scope);
|
||||
await resolveCalculationNode(calcObj, calcObj.parseNode, computation.scope);
|
||||
|
||||
// link and aggregate the effects and proficiencies
|
||||
// link the effects and proficiencies
|
||||
linkCalculationEffects(node, computation);
|
||||
aggregateCalculationEffects(calcObj, id => computation.propsById[id]);
|
||||
linkCalculationProficiencies(node, computation)
|
||||
aggregateCalculationProficiencies(calcObj, id => computation.propsById[id], computation.scope['proficiencyBonus']?.value || 0);
|
||||
|
||||
// Store the unaffected value
|
||||
if (calcObj.effectIds || calcObj.proficiencyIds) {
|
||||
calcObj.unaffected = toPrimitiveOrString(calcObj.valueNode);
|
||||
}
|
||||
|
||||
// Aggregate the effects and proficiencies
|
||||
aggregateCalculationEffects(calcObj, id => computation.propsById[id]);
|
||||
aggregateCalculationProficiencies(calcObj, id => computation.propsById[id], computation.scope['proficiencyBonus']?.value || 0);
|
||||
|
||||
// Resolve the valueNode after effects and proficiencies have been applied to it
|
||||
resolveCalculationNode(calcObj, calcObj.valueNode, computation.scope);
|
||||
await resolveCalculationNode(calcObj, calcObj.valueNode, computation.scope);
|
||||
|
||||
// Store the value as a primitive
|
||||
calcObj.value = toPrimitiveOrString(calcObj.valueNode);
|
||||
@@ -32,10 +35,13 @@ export default function computeCalculation(computation, node) {
|
||||
delete calcObj._localScope;
|
||||
}
|
||||
|
||||
export function resolveCalculationNode(calculation, parseNode, scope, givenContext) {
|
||||
export async function resolveCalculationNode(calculation, parseNode, scope, givenContext) {
|
||||
if (!parseNode) throw new Error('parseNode is required');
|
||||
const fn = calculation._parseLevel;
|
||||
const calculationScope = { ...calculation._localScope, ...scope };
|
||||
const { result: resultNode, context } = resolve(fn, parseNode, calculationScope, givenContext);
|
||||
const { result: resultNode, context } = await resolve(fn, parseNode, calculationScope, givenContext);
|
||||
if (calculation.hash === 1318417319946211 && calculation._key === 'attackRoll') console.log({ calculation, resultNode, parseNode, ers: context.errors })
|
||||
|
||||
calculation.errors = context.errors;
|
||||
calculation.valueNode = resultNode;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { has } from 'lodash';
|
||||
import { resolveCalculationNode } from '/imports/api/engine/computation/computeComputation/computeByType/computeCalculation';
|
||||
|
||||
export default function computePointBuy(computation, node) {
|
||||
export default async function computePointBuy(computation, node) {
|
||||
const prop = node.data;
|
||||
const min = has(prop, 'min.value') ? prop.min.value : null;
|
||||
const max = has(prop, 'max.value') ? prop.max.value : null;
|
||||
prop.spent = 0;
|
||||
prop.values?.forEach(row => {
|
||||
|
||||
for (const row of prop.values || []) {
|
||||
row.spent = 0;
|
||||
if (row.value === undefined) return;
|
||||
const costFunction = EJSON.clone(prop.cost);
|
||||
@@ -22,7 +21,7 @@ export default function computePointBuy(computation, node) {
|
||||
}
|
||||
// Evaluate the cost function
|
||||
if (!costFunction) return;
|
||||
resolveCalculationNode(costFunction, costFunction.parseNode, {
|
||||
await resolveCalculationNode(costFunction, costFunction.parseNode, {
|
||||
...computation.scope, value: row.value
|
||||
});
|
||||
// Write calculation errors
|
||||
@@ -37,7 +36,7 @@ export default function computePointBuy(computation, node) {
|
||||
row.spent = costFunction.value;
|
||||
prop.spent += costFunction.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
prop.pointsLeft = (prop.total?.value || 0) - (prop.spent || 0);
|
||||
if (prop.spent > prop.total?.value) {
|
||||
prop.errors = prop.errors || [];
|
||||
|
||||
@@ -8,11 +8,9 @@ export default function computeVariableAsAttribute(computation, node, prop) {
|
||||
// Apply damage in a way that respects the damage rules, modifying damage if need be
|
||||
// Bound the damage
|
||||
if (!prop.ignoreLowerLimit && prop.damage > prop.total) {
|
||||
console.log(`reducing damage from ${prop.damage} to ${prop.total}`);
|
||||
prop.damage = prop.total;
|
||||
}
|
||||
if (!prop.ignoreUpperLimit && prop.damage < 0) {
|
||||
console.log(`increasing damage from ${prop.damage} to 0`);
|
||||
prop.damage = 0;
|
||||
}
|
||||
// Apply damage
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import getAggregatorResult from './getAggregatorResult';
|
||||
|
||||
export default function computeVariableAsToggle(computation, node, prop) {
|
||||
let result = getAggregatorResult(node, prop) || 0;
|
||||
let result = getAggregatorResult(node) || 0;
|
||||
|
||||
prop.value = !!result || !!prop.enabled || !!prop.condition?.value;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
|
||||
const prop = computation.propsById['actionId'];
|
||||
assert.equal(prop.summary.value, 'test summary 3 without referencing anything 7');
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
const scope = variableName => computation.scope[variableName];
|
||||
assert.equal(prop('emptyId').value, 0, 'calculates empty props to zero');
|
||||
|
||||
@@ -3,10 +3,13 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation.js';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
// Tag targeted effects make complicated parse trees
|
||||
console.log(prop('attackAction2'));
|
||||
assert.equal(prop('attackAction2').attackRoll.value, 'min(3 + d4, d100)', 'Tag targeted effects change the attack roll correctly');
|
||||
// Tags target effects on attributes
|
||||
assert.equal(prop('taggedCon').value, 26, 'Tagged targeted effects affect attribute values');
|
||||
assert.equal(prop('taggedCon').baseValue.value, 10, 'Tag targeted effects target the attribute itself, not the base value');
|
||||
@@ -14,7 +17,6 @@ export default function () {
|
||||
assert.equal(prop('attackAction').attackRoll.value, 20, 'Tag targeted effects change the attack roll correctly');
|
||||
// Tag target effects can deal with rolls
|
||||
assert.equal(prop('attackAction').attackRoll.value, 20, 'Tag targeted effects change the attack roll correctly');
|
||||
assert.equal(prop('attackAction2').attackRoll.value, 'min(3 + d4, d100)', 'Tag targeted effects change the attack roll correctly');
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const scope = id => computation.scope[id];
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(scope('level').value, 5);
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(prop('attId').value, 6);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const scope = id => computation.scope[id];
|
||||
assert.isTrue(scope('blugeoning').vulnerability);
|
||||
assert.isTrue(scope('customDamage').resistance);
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(prop('strengthId').value, 26);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties, compareOrder } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
const scope = id => computation.scope[id].value;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation.js';
|
||||
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn.js';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(prop('strengthId').value, 11, 'Point buys should apply a base value when active');
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties, compareOrder } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const hasLink = computation.dependencyGraph.hasLink;
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(
|
||||
prop('strengthId').value, 8,
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
|
||||
assert.equal(prop('atheleticsId').proficiency, 2, 'Inherits proficiency from ability');
|
||||
|
||||
@@ -4,7 +4,7 @@ import embedInlineCalculations from './utility/embedInlineCalculations';
|
||||
import { removeEmptyCalculations } from './buildComputation/parseCalculationFields';
|
||||
import path from 'ngraph.path';
|
||||
|
||||
export default function computeCreatureComputation(computation) {
|
||||
export default async function computeCreatureComputation(computation) {
|
||||
const stack = [];
|
||||
// Computation scope of {variableName: prop}
|
||||
const graph = computation.dependencyGraph;
|
||||
@@ -31,7 +31,7 @@ export default function computeCreatureComputation(computation) {
|
||||
top._visited = true;
|
||||
stack.pop();
|
||||
// Compute the top object of the stack
|
||||
compute(computation, top);
|
||||
await compute(computation, top);
|
||||
} else {
|
||||
top._visitedChildren = true;
|
||||
// Push dependencies to graph to be computed first
|
||||
@@ -40,14 +40,16 @@ export default function computeCreatureComputation(computation) {
|
||||
}
|
||||
|
||||
// Finish the props after the dependency graph has been traversed
|
||||
computation.props.forEach(finalizeProp);
|
||||
for (const prop of computation.props) {
|
||||
finalizeProp(prop);
|
||||
}
|
||||
}
|
||||
|
||||
function compute(computation, node) {
|
||||
async function compute(computation, node) {
|
||||
// Determine the prop's active status by its toggles
|
||||
computeToggles(computation, node);
|
||||
// Compute the property by type
|
||||
computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
await computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
}
|
||||
|
||||
function pushDependenciesToStack(nodeId, graph, stack, computation) {
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
import { get } from 'lodash';
|
||||
|
||||
export default function applyFnToKey(doc, key, fn){
|
||||
if (key.includes('.$')){
|
||||
export default function applyFnToKey(doc, key, fn) {
|
||||
if (key.includes('.$')) {
|
||||
applyToArrayKey(doc, key, fn);
|
||||
} else {
|
||||
applyToSingleKey(doc, key, fn);
|
||||
}
|
||||
}
|
||||
|
||||
function applyToSingleKey(doc, key, fn){
|
||||
export async function applyFnToKeyAsync(doc, key, fn) {
|
||||
if (key.includes('.$')) {
|
||||
await applyToArrayKeyAsync(doc, key, fn);
|
||||
} else {
|
||||
await applyToSingleKeyAsync(doc, key, fn);
|
||||
}
|
||||
}
|
||||
|
||||
function applyToSingleKey(doc, key, fn) {
|
||||
// call the function with the current value and document for context
|
||||
fn(doc, key);
|
||||
return fn(doc, key);
|
||||
}
|
||||
|
||||
async function applyToSingleKeyAsync(doc, key, fn) {
|
||||
// call the function with the current value and document for context
|
||||
return await fn(doc, key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,7 +32,7 @@ function applyToSingleKey(doc, key, fn){
|
||||
* Warning: Order might be confusing, it will traverse the deepest array in order
|
||||
* but the shallower arrays in reverse order
|
||||
*/
|
||||
function applyToArrayKey(doc, key, fn){
|
||||
function applyToArrayKey(doc, key, fn) {
|
||||
const keySplit = key.split('.$');
|
||||
// Stack based depth first traversal of arrays
|
||||
const array = get(doc, keySplit[0]);
|
||||
@@ -30,11 +43,12 @@ function applyToArrayKey(doc, key, fn){
|
||||
currentPath: keySplit[0],
|
||||
indices: [],
|
||||
}];
|
||||
while(stack.length){
|
||||
while (stack.length) {
|
||||
const state = stack.pop();
|
||||
for (let index in state.array){
|
||||
if (!state) break;
|
||||
for (let index in state.array) {
|
||||
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
||||
if (state.paths.length == 1){
|
||||
if (state.paths.length == 1) {
|
||||
applyToSingleKey(doc, currentPath, fn);
|
||||
} else {
|
||||
const array = get(doc, currentPath);
|
||||
@@ -49,3 +63,35 @@ function applyToArrayKey(doc, key, fn){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyToArrayKeyAsync(doc, key, fn) {
|
||||
const keySplit = key.split('.$');
|
||||
// Stack based depth first traversal of arrays
|
||||
const array = get(doc, keySplit[0]);
|
||||
if (!array) return;
|
||||
const stack = [{
|
||||
array,
|
||||
paths: keySplit.slice(1),
|
||||
currentPath: keySplit[0],
|
||||
indices: [],
|
||||
}];
|
||||
while (stack.length) {
|
||||
const state = stack.pop();
|
||||
if (!state) break;
|
||||
for (let index in state.array) {
|
||||
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
||||
if (state.paths.length == 1) {
|
||||
await applyToSingleKey(doc, currentPath, fn);
|
||||
} else {
|
||||
const array = get(doc, currentPath);
|
||||
if (!array) return;
|
||||
stack.push({
|
||||
array,
|
||||
paths: state.paths.slice(1),
|
||||
currentPath,
|
||||
indices: [...state.indices, index],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ import writeAlteredProperties from './computation/writeComputation/writeAlteredP
|
||||
import writeScope from './computation/writeComputation/writeScope';
|
||||
import writeErrors from './computation/writeComputation/writeErrors';
|
||||
|
||||
export default function computeCreature(creatureId) {
|
||||
export default async function computeCreature(creatureId) {
|
||||
if (Meteor.isClient) return;
|
||||
// console.log('compute ' + creatureId);
|
||||
const computation = buildCreatureComputation(creatureId);
|
||||
computeComputation(computation, creatureId);
|
||||
await computeComputation(computation, creatureId);
|
||||
}
|
||||
|
||||
function computeComputation(computation, creatureId) {
|
||||
async function computeComputation(computation, creatureId) {
|
||||
try {
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
writeAlteredProperties(computation);
|
||||
writeScope(creatureId, computation);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user