Fixed failing action engine tests, moved more engine parts to ts
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { assert } from 'chai';
|
||||
import {
|
||||
allMutations,
|
||||
allUpdates,
|
||||
createTestCreature,
|
||||
randomIds,
|
||||
removeAllCreaturesAndProps,
|
||||
runActionById
|
||||
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
|
||||
import { Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
|
||||
const [
|
||||
creatureId, targetCreatureId, targetCreature2Id,
|
||||
@@ -30,7 +32,7 @@ const actionTestCreature = {
|
||||
{
|
||||
_id: attackMissId,
|
||||
type: 'action',
|
||||
attackRoll: { calculation: '-20' },
|
||||
attackRoll: { calculation: '-5' },
|
||||
},
|
||||
// Disable crits
|
||||
{
|
||||
@@ -105,48 +107,57 @@ describe('Apply Action Properties', function () {
|
||||
targetCreatureId,
|
||||
targetCreature2Id,
|
||||
]);
|
||||
assert.deepEqual(allMutations(action), [{
|
||||
contents: [{
|
||||
name: 'Action'
|
||||
}],
|
||||
targetIds: [
|
||||
targetCreatureId,
|
||||
targetCreature2Id,
|
||||
]
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Hit!',
|
||||
value: '1d20 [10] + 10\n**20**',
|
||||
}],
|
||||
targetIds: [targetCreatureId],
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Hit!',
|
||||
value: '1d20 [10] + 10\n**20**',
|
||||
}],
|
||||
targetIds: [targetCreature2Id],
|
||||
}]);
|
||||
const expectedMutations: Mutation[] = [
|
||||
{
|
||||
contents: [{ name: 'Action' }],
|
||||
targetIds: [targetCreatureId, targetCreature2Id]
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Hit!',
|
||||
value: '1d20 [10] + 10\n**20**',
|
||||
}],
|
||||
targetIds: [targetCreatureId],
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Hit!',
|
||||
value: '1d20 [10] + 10\n**20**',
|
||||
}],
|
||||
targetIds: [targetCreature2Id],
|
||||
},
|
||||
];
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
it('should make attack rolls that use uses', async function () {
|
||||
const action = await runActionById(usesActionId, [targetCreatureId]);
|
||||
const expectedUpdates: Update[] = [
|
||||
{
|
||||
propId: usesActionId,
|
||||
type: 'action',
|
||||
inc: { usesUsed: 1, usesLeft: -1 },
|
||||
}
|
||||
]
|
||||
assert.deepEqual(allUpdates(action), expectedUpdates);
|
||||
});
|
||||
|
||||
it('should make attack rolls that miss', async function () {
|
||||
const action = await runActionById(attackMissId, [targetCreatureId]);
|
||||
assert.deepEqual(allMutations(action), [{
|
||||
contents: [{
|
||||
name: 'Action'
|
||||
}],
|
||||
targetIds: [
|
||||
targetCreatureId,
|
||||
]
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Miss!',
|
||||
value: '1d20 [10] + 10\n**20**',
|
||||
}],
|
||||
targetIds: [targetCreatureId],
|
||||
}]);
|
||||
const expectedMutations: Mutation[] = [
|
||||
{
|
||||
contents: [{ name: 'Action' }],
|
||||
targetIds: [targetCreatureId],
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Miss!',
|
||||
value: '1d20 [10] − 5\n**5**', // DiceCloud uses unicode minus
|
||||
}],
|
||||
targetIds: [targetCreatureId],
|
||||
}
|
||||
];
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -10,11 +10,12 @@ import { applyAfterChildrenTriggers, applyAfterTriggers, applyChildren } from '/
|
||||
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import rollDice from '/imports/parser/rollDice';
|
||||
import { getFromScope, getNumberFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import { getNumberFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
|
||||
|
||||
export default async function applyActionProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
const prop = task.prop;
|
||||
const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
@@ -48,13 +49,13 @@ export default async function applyActionProperty(
|
||||
|
||||
spendResources(action, prop, targetIds, result, userInput);
|
||||
|
||||
const attack = prop.attackRoll || prop.attackRollBonus;
|
||||
const attack: CalculatedField = prop.attackRoll || prop.attackRollBonus;
|
||||
|
||||
// Attack if there is an attack roll
|
||||
if (attack && attack.calculation) {
|
||||
if (targetIds.length) {
|
||||
for (const targetId of targetIds) {
|
||||
await applyAttackToTarget(task, action, attack, targetId, result);
|
||||
await applyAttackToTarget(task, action, attack, targetId, result, userInput);
|
||||
await applyAfterTriggers(action, prop, [targetId], userInput);
|
||||
await applyChildren(action, prop, [targetId], userInput);
|
||||
}
|
||||
@@ -76,7 +77,8 @@ export default async function applyActionProperty(
|
||||
}
|
||||
|
||||
async function applyAttackToTarget(
|
||||
task: PropTask, action: EngineAction, attack, targetId, taskResult: TaskResult
|
||||
task: PropTask, action: EngineAction, attack: CalculatedField, targetId: string,
|
||||
taskResult: TaskResult, userInput: InputProvider
|
||||
) {
|
||||
taskResult.pushScope = {
|
||||
'~attackHit': {},
|
||||
@@ -86,7 +88,7 @@ async function applyAttackToTarget(
|
||||
'~attackRoll': {},
|
||||
}
|
||||
|
||||
await recalculateCalculation(attack, action, 'reduce');
|
||||
await recalculateCalculation(attack, action, 'reduce', userInput);
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
const contents: LogContent[] = [];
|
||||
|
||||
@@ -95,7 +97,7 @@ async function applyAttackToTarget(
|
||||
result,
|
||||
criticalHit,
|
||||
criticalMiss,
|
||||
} = await rollAttack(attack, scope, taskResult.pushScope);
|
||||
} = await rollAttack(attack, scope, taskResult.pushScope, userInput);
|
||||
|
||||
const targetScope = getVariables(targetId);
|
||||
const targetArmor = getNumberFromScope('armor', targetScope)
|
||||
@@ -143,7 +145,7 @@ async function applyAttackToTarget(
|
||||
}
|
||||
}
|
||||
|
||||
async function applyAttackWithoutTarget(action, prop, attack, taskResult: TaskResult, userInput) {
|
||||
async function applyAttackWithoutTarget(action, prop, attack, taskResult: TaskResult, userInput: InputProvider) {
|
||||
taskResult.pushScope = {
|
||||
'~attackHit': {},
|
||||
'~attackMiss': {},
|
||||
@@ -151,14 +153,14 @@ async function applyAttackWithoutTarget(action, prop, attack, taskResult: TaskRe
|
||||
'~criticalMiss': {},
|
||||
'~attackRoll': {},
|
||||
}
|
||||
await recalculateCalculation(attack, action, 'reduce');
|
||||
await recalculateCalculation(attack, action, 'reduce', userInput);
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
const {
|
||||
resultPrefix,
|
||||
result,
|
||||
criticalHit,
|
||||
criticalMiss,
|
||||
} = await rollAttack(attack, scope, taskResult.pushScope);
|
||||
} = await rollAttack(attack, scope, taskResult.pushScope, userInput);
|
||||
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
name += ' (Advantage)';
|
||||
@@ -182,11 +184,11 @@ async function applyAttackWithoutTarget(action, prop, attack, taskResult: TaskRe
|
||||
});
|
||||
}
|
||||
|
||||
async function rollAttack(attack, scope, resultPushScope) {
|
||||
async function rollAttack(attack, scope, resultPushScope, userInput: InputProvider) {
|
||||
const rollModifierText = numberToSignedString(attack.value, true);
|
||||
let value, resultPrefix;
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
const [a, b] = await rollDice(2, 20);
|
||||
const [[a, b]] = await userInput.rollDice(attack, [{ number: 2, diceSize: 20 }]);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
@@ -195,7 +197,7 @@ async function rollAttack(attack, scope, resultPushScope) {
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
const [a, b] = await rollDice(2, 20);
|
||||
const [[a, b]] = await userInput.rollDice(attack, [{ number: 2, diceSize: 20 }]);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
@@ -204,7 +206,7 @@ async function rollAttack(attack, scope, resultPushScope) {
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else {
|
||||
value = await rollDice(1, 20)[0];
|
||||
[[value]] = await userInput.rollDice(attack, [{ number: 1, diceSize: 20 }]);
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
||||
}
|
||||
resultPushScope['~attackDiceRoll'] = { value };
|
||||
@@ -231,7 +233,7 @@ function applyCrits(value, scope, resultPushScope) {
|
||||
return { criticalHit, criticalMiss };
|
||||
}
|
||||
|
||||
async function resetProperties(action: EngineAction, prop: any, result: TaskResult, userInput) {
|
||||
async function resetProperties(action: EngineAction, prop: any, result: TaskResult, userInput: InputProvider) {
|
||||
const attributes = getPropertiesOfType(action.creatureId, 'attribute');
|
||||
for (const att of attributes) {
|
||||
if (att.removed || att.inactive) continue;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import { applyDefaultAfterPropTasks, applyTaskToEachTarget } 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';
|
||||
@@ -8,7 +9,7 @@ import { getSingleProperty, getVariables } from '/imports/api/engine/loadCreatur
|
||||
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
||||
|
||||
export default async function applyAdjustmentProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
const prop = task.prop;
|
||||
const damageTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import { applyAfterPropTasksForSingleChild, applyAfterTasksSkipChildren, applyDefaultAfterPropTasks, applyTaskToEachTarget } 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';
|
||||
@@ -8,7 +9,7 @@ import { getPropertyChildren } from '/imports/api/engine/loadCreatures';
|
||||
import rollDice from '/imports/parser/rollDice';
|
||||
|
||||
export default async function applyBranchProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
const prop = task.prop;
|
||||
const targets = task.targetIds;
|
||||
|
||||
@@ -16,9 +16,10 @@ import recalculateInlineCalculations from '/imports/api/engine/action/functions/
|
||||
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX';
|
||||
import { applyAfterTasksSkipChildren } from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
|
||||
export default async function applyBuffProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider
|
||||
) {
|
||||
const prop = EJSON.clone(task.prop);
|
||||
const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
|
||||
9
app/imports/api/engine/action/functions/InputProvider.ts
Normal file
9
app/imports/api/engine/action/functions/InputProvider.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
|
||||
type InputProvider = {
|
||||
rollDice(
|
||||
action: EngineAction, dice: { number: number, diceSize: number }[]
|
||||
): Promise<number[][]>;
|
||||
}
|
||||
|
||||
export default InputProvider;
|
||||
@@ -1,4 +1,3 @@
|
||||
import { assert } from 'chai';
|
||||
import '/imports/api/simpleSchemaConfig.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn';
|
||||
@@ -9,6 +8,7 @@ import { loadCreature } from '/imports/api/engine/loadCreatures';
|
||||
import EngineActions, { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { applyAction } from '/imports/api/engine/action/functions/applyAction';
|
||||
import { LogContent, Mutation, Removal, Update } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
|
||||
/**
|
||||
* Removes all creatures, properties, and creatureVariable documents from the database
|
||||
@@ -59,12 +59,12 @@ export const randomIds = new Array(100).fill(undefined).map(() => Random.id());
|
||||
* @param userInputFn A function that simulates user input
|
||||
* @returns The Engine Action with mutations resulting from running the action
|
||||
*/
|
||||
export async function runActionById(propId, targetIds?, userInputFn = () => 0) {
|
||||
export async function runActionById(propId, targetIds?, userInput = testInputProvider) {
|
||||
const prop = await CreatureProperties.findOneAsync(propId);
|
||||
const actionId = await createAction(prop, targetIds);
|
||||
const action = await EngineActions.findOneAsync(actionId);
|
||||
if (!action) throw 'Action is expected to exist';
|
||||
await applyAction(action, userInputFn, { simulate: true });
|
||||
await applyAction(action, userInput, { simulate: true });
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -148,3 +148,25 @@ export function allLogContent(action: EngineAction) {
|
||||
});
|
||||
return contents;
|
||||
}
|
||||
|
||||
const testInputProvider: InputProvider = {
|
||||
/**
|
||||
* For testing, randomness is hard to deal with
|
||||
* rollDice function returns the average roll for every dice rolled
|
||||
* [5d10, 1d4] => [[6,6,6,6,6], [3]]
|
||||
*/
|
||||
async rollDice(action, dice) {
|
||||
const result: number[][] = [];
|
||||
for (const diceRoll of dice) {
|
||||
const averageRoll = Math.round(diceRoll.diceSize / 2);
|
||||
// Return an array full of averagely rolled dice
|
||||
result.push(
|
||||
new Array(diceRoll.number)
|
||||
.fill(averageRoll)
|
||||
)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export { testInputProvider }
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EngineAction, ActionSchema } from '/imports/api/engine/action/EngineActions';
|
||||
import EngineActions, { EngineAction, ActionSchema } from '/imports/api/engine/action/EngineActions';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask'
|
||||
import { isEmpty } from 'lodash';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
|
||||
// TODO create a function to get the effective value of a property,
|
||||
// simulating all the result updates in the action so far
|
||||
@@ -10,12 +11,12 @@ import { isEmpty } from 'lodash';
|
||||
// This is run once as a simulation on the client awaiting all the various inputs or step through
|
||||
// clicks from the user, then it is run as part of the runAction method, where it is expected to
|
||||
// complete instantly on the client, and sent to the server as a method call
|
||||
export async function applyAction(action: EngineAction, userInput?: any[] | Function, options?: {
|
||||
export async function applyAction(action: EngineAction, userInput: InputProvider, options?: {
|
||||
simulate?: boolean, stepThrough?: boolean
|
||||
}) {
|
||||
const { simulate, stepThrough } = options || {};
|
||||
if (!simulate && stepThrough) throw 'Cannot step through unless simulating';
|
||||
if (simulate && typeof userInput !== 'function') throw 'Must provide a function to get user input when simulating';
|
||||
if (simulate && !userInput) throw 'Must provide a function to get user input when simulating';
|
||||
|
||||
action._stepThrough = stepThrough;
|
||||
action._isSimulation = simulate;
|
||||
@@ -37,6 +38,6 @@ function writeChangedAction(original: EngineAction, changed: EngineAction) {
|
||||
}
|
||||
}
|
||||
if (!isEmpty($set) && original._id) {
|
||||
return Actions.updateAsync(original._id, { $set });
|
||||
return EngineActions.updateAsync(original._id, { $set });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,33 @@ import { Context, toPrimitiveOrString } from '/imports/parser/resolve';
|
||||
import {
|
||||
aggregateCalculationEffects,
|
||||
aggregateCalculationProficiencies,
|
||||
resolveCalculationNode,
|
||||
} from '/imports/api/engine/computation/computeComputation/computeByType/computeCalculation';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
|
||||
import { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
|
||||
// TODO Redo the work of
|
||||
// imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js
|
||||
// But in the action scope
|
||||
export default async function recalculateCalculation(
|
||||
calcObj, action, parseLevel = 'reduce', context, scope
|
||||
calcObj: CalculatedField,
|
||||
action,
|
||||
parseLevel: ResolveLevel = 'reduce',
|
||||
userInput: InputProvider,
|
||||
) {
|
||||
if (!calcObj?.parseNode) return;
|
||||
calcObj._parseLevel = parseLevel;
|
||||
if (!scope) {
|
||||
scope = await getEffectiveActionScope(action);
|
||||
}
|
||||
// Re-resolve the parse node
|
||||
resolveCalculationNode(calcObj, calcObj.parseNode, scope, context);
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
// Re-resolve the parse node before effects and proficiencies
|
||||
const {
|
||||
result: unaffectedResult,
|
||||
context
|
||||
} = resolve(parseLevel, calcObj.parseNode, scope);
|
||||
calcObj.valueNode = unaffectedResult;
|
||||
|
||||
// store the unaffected value
|
||||
if (calcObj.effectIds || calcObj.proficiencyIds) {
|
||||
calcObj.unaffected = toPrimitiveOrString(calcObj.valueNode);
|
||||
@@ -35,19 +43,28 @@ export default async function recalculateCalculation(
|
||||
id => getSingleProperty(action.creatureId, id),
|
||||
scope['proficiencyBonus']?.value || 0
|
||||
);
|
||||
// Resolve the modified valueNode
|
||||
resolveCalculationNode(calcObj, calcObj.valueNode, scope, context);
|
||||
|
||||
// Store the primitive value
|
||||
calcObj.value = toPrimitiveOrString(calcObj.valueNode);
|
||||
// TODO log errors
|
||||
// Resolve the modified valueNode, use the same context
|
||||
const {
|
||||
result: finalResult
|
||||
} = resolve(parseLevel, calcObj.parseNode, scope, context);
|
||||
|
||||
// Store the errors
|
||||
calcObj.errors = context.errors;
|
||||
|
||||
// Store the value and its primitive
|
||||
calcObj.value = toPrimitiveOrString(finalResult);
|
||||
calcObj.valueNode = finalResult;
|
||||
|
||||
}
|
||||
|
||||
export async function rollAndReduceCalculation(calcObj, action) {
|
||||
export async function rollAndReduceCalculation(
|
||||
calcObj: CalculatedField, action: EngineAction, userInput: InputProvider
|
||||
) {
|
||||
const context = new Context();
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
// Compile
|
||||
recalculateCalculation(calcObj, action, 'compile', context, scope);
|
||||
recalculateCalculation(calcObj, action, 'compile', userInput);
|
||||
const compiled = calcObj.valueNode;
|
||||
|
||||
// Roll
|
||||
|
||||
@@ -5,8 +5,11 @@ import applyDamagePropTask from '/imports/api/engine/action/tasks/applyDamagePro
|
||||
import applyItemAsAmmoTask from '/imports/api/engine/action/tasks/applyItemAsAmmoTask';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import applyProperties from '/imports/api/engine/action/applyProperties';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
|
||||
export default async function applyTask(action: EngineAction, task: Task, userInput?): Promise<void> {
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: Task, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
action.taskCount += 1;
|
||||
if (action.taskCount > 100) throw 'Only 100 properties can be applied at once';
|
||||
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import { ConstantValueType } from '/imports/parser/parseTree/constant';
|
||||
|
||||
export interface CalculatedField {
|
||||
calculation?: string,
|
||||
value?: string | number,
|
||||
effectIds?: string[],
|
||||
parseNode?: any,
|
||||
parseError?: any,
|
||||
hash?: number,
|
||||
errors?: any[],
|
||||
calculation?: string;
|
||||
value?: ConstantValueType;
|
||||
valueNode: ParseNode;
|
||||
effectIds?: string[];
|
||||
proficiencyIds?: string[];
|
||||
unaffected?: ConstantValueType;
|
||||
parseNode?: ParseNode;
|
||||
parseError?: any;
|
||||
hash?: number;
|
||||
errors?: any[];
|
||||
}
|
||||
|
||||
// Get schemas that apply fields directly so they can be gracefully extended
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
import { Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
|
||||
type ConstantValueType = number | string | boolean | undefined
|
||||
export type ConstantValueType = number | string | boolean | undefined
|
||||
|
||||
export type ConstantNode = {
|
||||
parseType: 'constant';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import nodeTypeIndex from './parseTree/_index';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import { ConstantValueType } from '/imports/parser/parseTree/constant';
|
||||
|
||||
// Takes a parse node and computes it to a set detail level
|
||||
// returns {result, context}
|
||||
@@ -37,10 +38,10 @@ export function toString(node: ParseNode) {
|
||||
return type.toString(node);
|
||||
}
|
||||
|
||||
export function toPrimitiveOrString(node) {
|
||||
export function toPrimitiveOrString(node: ParseNode): ConstantValueType {
|
||||
if (!node) return '';
|
||||
if (node.parseType === 'constant') return node.value;
|
||||
if (node.parseType === 'error') return null;
|
||||
if (node.parseType === 'error') return undefined;
|
||||
return toString(node);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export default function rollDice(number, diceSize){
|
||||
let values = [];
|
||||
let randomSrc = DDP.randomStream('diceRoller');
|
||||
for (let i = 0; i < number; i++){
|
||||
let roll = ~~(randomSrc.fraction() * diceSize) + 1
|
||||
values.push(roll);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
12
app/imports/parser/rollDice.ts
Normal file
12
app/imports/parser/rollDice.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function rollDice(number: number, diceSize: number): number[] {
|
||||
const values: number[] = [];
|
||||
const randomSrc = DDP.randomStream('diceRoller');
|
||||
if (number > 100) {
|
||||
throw new Meteor.Error('Too many dice', 'can only roll up to 100 dice at once');
|
||||
}
|
||||
for (let i = 0; i < number; i++) {
|
||||
const roll = ~~(randomSrc.fraction() * diceSize) + 1
|
||||
values.push(roll);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
Reference in New Issue
Block a user