diff --git a/app/imports/api/engine/action/functions/InputProvider.ts b/app/imports/api/engine/action/functions/InputProvider.ts index 90923766..51244a25 100644 --- a/app/imports/api/engine/action/functions/InputProvider.ts +++ b/app/imports/api/engine/action/functions/InputProvider.ts @@ -1,4 +1,10 @@ +import Task from '/imports/api/engine/action/tasks/Task'; + type InputProvider = { + /** + * Show the user the next property or task to apply and wait for input to continue + */ + nextStep?(task: Task): Promise; /** * Roll dice * @param dice How many dice @@ -20,7 +26,23 @@ type InputProvider = { /** * Get advantage, natural, or disadvantage for a d20 roll */ - advantage(suggestedAdvantage: 0 | 1 | -1): Promise<0 | 1 | -1>; + advantage(suggestedAdvantage: Advantage): Promise; + /** + * Get the details of a check or save + */ + check(suggestedParams: CheckParams): Promise; } -export default InputProvider; \ No newline at end of file +export type Advantage = 0 | 1 | -1; + +export type CheckParams = { + advantage: Advantage; + skillVariableName: string; + abilityVariableName: string; + dc: number | null; + contest?: true; + targetSkillVariableName?: string; + targetAbilityVariableName?: string; +} + +export default InputProvider; diff --git a/app/imports/api/engine/action/functions/applyAction.ts b/app/imports/api/engine/action/functions/applyAction.ts index e28bf562..0e00d547 100644 --- a/app/imports/api/engine/action/functions/applyAction.ts +++ b/app/imports/api/engine/action/functions/applyAction.ts @@ -20,6 +20,7 @@ export default async function applyAction(action: EngineAction, userInput: Input action._stepThrough = stepThrough; action._isSimulation = simulate; action.taskCount = 0; + console.log(JSON.stringify(action, null, 2)); const prop = await getSingleProperty(action.creatureId, action.rootPropId); if (!prop) throw new Meteor.Error('Not found', 'Root action property could not be found'); await applyTask(action, { diff --git a/app/imports/api/engine/action/methods/index.ts b/app/imports/api/engine/action/methods/index.ts new file mode 100644 index 00000000..d498cbc4 --- /dev/null +++ b/app/imports/api/engine/action/methods/index.ts @@ -0,0 +1,3 @@ +import './insertAction'; +import './runAction'; +import './updateAction'; diff --git a/app/imports/api/engine/action/methods/insertAction.ts b/app/imports/api/engine/action/methods/insertAction.ts index 1c85f4a6..ac0f0881 100644 --- a/app/imports/api/engine/action/methods/insertAction.ts +++ b/app/imports/api/engine/action/methods/insertAction.ts @@ -4,7 +4,7 @@ import EngineActions, { EngineAction, ActionSchema } from '/imports/api/engine/a import { assertEditPermission } from '/imports/api/sharing/sharingPermissions'; import { getCreature } from '/imports/api/engine/loadCreatures'; -export const insertAction: ValidatedMethod = new ValidatedMethod({ +export const insertAction = new ValidatedMethod({ name: 'actions.insertAction', validate: new SimpleSchema({ action: ActionSchema @@ -16,6 +16,6 @@ export const insertAction: ValidatedMethod = new ValidatedMethod({ EngineActions.removeAsync({ creatureId: action.creatureId }); // Force a random id even if one was provided, we may use it later as the seed for PRNG delete action._id; - return await EngineActions.insertAsync(action); + return EngineActions.insertAsync(action); }, }); diff --git a/app/imports/api/engine/action/methods/updateAction.ts b/app/imports/api/engine/action/methods/updateAction.ts new file mode 100644 index 00000000..cdb4998d --- /dev/null +++ b/app/imports/api/engine/action/methods/updateAction.ts @@ -0,0 +1,22 @@ +import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import EngineActions from '/imports/api/engine/action/EngineActions'; +import { assertEditPermission } from '/imports/api/sharing/sharingPermissions'; +import { getCreature } from '/imports/api/engine/loadCreatures'; + +export const updateAction = new ValidatedMethod({ + name: 'actions.updateAction', + validate({ _id, path, value }) { + if (!_id) throw new Meteor.Error('No _id', '_id is required'); + // We cannot change these fields with a simple update + if (path !== 'targetIds') throw new Meteor.Error('Can only update target ids'); + if (!Array.isArray(value)) throw new Meteor.Error('TargetIds must be an array'); + }, + run: async function ({ _id, path, value }: { _id: string, path: 'targetIds', value: string[] }) { + const action = await EngineActions.findOneAsync(_id); + if (!action) { + throw new Meteor.Error('not found', 'The given action was not found'); + } + assertEditPermission(getCreature(action.creatureId), this.userId); + return EngineActions.updateAsync(_id, { $set: { [path]: value } }); + }, +}); diff --git a/app/imports/api/engine/action/tasks/applyCheckTask.ts b/app/imports/api/engine/action/tasks/applyCheckTask.ts new file mode 100644 index 00000000..e7fab1af --- /dev/null +++ b/app/imports/api/engine/action/tasks/applyCheckTask.ts @@ -0,0 +1,50 @@ +import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables'; +import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import InputProvider, { CheckParams } 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'; + +// TODO implement this +/** + * A skill property is applied as a check or a saving throw + */ +export default async function applyCheckTask( + task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider +): Promise { + + throw new Meteor.Error('Not implemented', 'This function needs to be implemented'); + + const prop = task.prop; + const targetIds = task.targetIds; + if (targetIds?.length) { + throw new Meteor.Error('too-many-targets', + 'This function is only implemented for zero targets'); + } + + let checkParams: CheckParams = { + advantage: 0, + skillVariableName: prop.variableName, + abilityVariableName: prop.ability, + dc: null, + } + + checkParams = await inputProvider.check(checkParams); + + const dc = checkParams.dc; + if (!prop.silent && dc !== null) result.appendLog({ + name: prop.name, + value: `DC **${dc}**`, + inline: true, + ...prop.silent && { silenced: prop.silent } + }, targetIds); + const scope = await getEffectiveActionScope(action); + + return applyDefaultAfterPropTasks(action, prop, targetIds, inputProvider); +} diff --git a/app/imports/api/engine/action/tasks/applyTask.ts b/app/imports/api/engine/action/tasks/applyTask.ts index 91a0b23b..1a9e4e16 100644 --- a/app/imports/api/engine/action/tasks/applyTask.ts +++ b/app/imports/api/engine/action/tasks/applyTask.ts @@ -20,6 +20,15 @@ export default async function applyTask( export default async function applyTask( action: EngineAction, task: Task, inputProvider: InputProvider ): Promise { + + // Pause and wait for the user if the action is being stepped through + console.log('applying task', action, inputProvider) + if (action._isSimulation && action._stepThrough && inputProvider.nextStep) { + console.log('waiting for next step resolution', task) + await inputProvider.nextStep(task); + } + + // Ensure no more than 100 tasks are performed by a single action action.taskCount += 1; if (action.taskCount > 100) throw 'Only 100 properties can be applied at once'; diff --git a/app/imports/client/ui/creature/actions/ActionDialog.vue b/app/imports/client/ui/creature/actions/ActionDialog.vue index 334b5571..73ea9806 100644 --- a/app/imports/client/ui/creature/actions/ActionDialog.vue +++ b/app/imports/client/ui/creature/actions/ActionDialog.vue @@ -10,11 +10,11 @@ {{ actionJson }} - +
+      
+        {{ resultJson }}
+      
+    
Step @@ -42,13 +43,12 @@ diff --git a/app/server/main.js b/app/server/main.js index 3e1f8725..11d21914 100644 --- a/app/server/main.js +++ b/app/server/main.js @@ -16,3 +16,4 @@ import '/imports/constants/MAINTENANCE_MODE'; import '/imports/api/creature/creatureProperties/methods/index'; import '/imports/api/creature/archive/methods/index'; import '/imports/api/creature/creatures/methods/index'; +import '/imports/api/engine/action/methods/index';