Action interruption progress
This commit is contained in:
@@ -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<void>;
|
||||
/**
|
||||
* 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<Advantage>;
|
||||
/**
|
||||
* Get the details of a check or save
|
||||
*/
|
||||
check(suggestedParams: CheckParams): Promise<CheckParams>;
|
||||
}
|
||||
|
||||
export default InputProvider;
|
||||
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;
|
||||
|
||||
@@ -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, {
|
||||
|
||||
3
app/imports/api/engine/action/methods/index.ts
Normal file
3
app/imports/api/engine/action/methods/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import './insertAction';
|
||||
import './runAction';
|
||||
import './updateAction';
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
22
app/imports/api/engine/action/methods/updateAction.ts
Normal file
22
app/imports/api/engine/action/methods/updateAction.ts
Normal file
@@ -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 } });
|
||||
},
|
||||
});
|
||||
50
app/imports/api/engine/action/tasks/applyCheckTask.ts
Normal file
50
app/imports/api/engine/action/tasks/applyCheckTask.ts
Normal file
@@ -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<void> {
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -20,6 +20,15 @@ export default async function applyTask(
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: Task, inputProvider: InputProvider
|
||||
): Promise<void | number> {
|
||||
|
||||
// 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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user