Finished move to 3 tier action methods
This commit is contained in:
@@ -9,9 +9,7 @@ import { toString } from '/imports/parser/resolve';
|
|||||||
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES';
|
import { getPropertyName } from '/imports/constants/PROPERTIES';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||||
import { use } from 'chai';
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
@@ -31,55 +29,17 @@ export interface Action {
|
|||||||
type Task = PropTask | DamagePropTask | ItemAsAmmoTask;
|
type Task = PropTask | DamagePropTask | ItemAsAmmoTask;
|
||||||
|
|
||||||
interface BaseTask {
|
interface BaseTask {
|
||||||
propId: string;
|
prop: { [key: string]: any };
|
||||||
targetIds: string[];
|
targetIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropTask extends BaseTask {
|
interface PropTask extends BaseTask {
|
||||||
step?: number,
|
|
||||||
subtaskFn?: undefined,
|
subtaskFn?: undefined,
|
||||||
beforeTriggersDone?: undefined | true;
|
|
||||||
taskScope?: {
|
|
||||||
[variableName: string]: { value: number },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DamagePropTask extends BaseTask {
|
class TaskResult {
|
||||||
subtaskFn: 'damageProp';
|
|
||||||
params: {
|
|
||||||
/**
|
|
||||||
* Use getPropertyTitle(prop) to set the title
|
|
||||||
*/
|
|
||||||
title?: string;
|
|
||||||
operation: 'increment' | 'set';
|
|
||||||
value: number;
|
|
||||||
prop: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ItemAsAmmoTask extends BaseTask {
|
|
||||||
subtaskFn: 'consumeItemAsAmmo';
|
|
||||||
params: {
|
|
||||||
/**
|
|
||||||
* Use getPropertyTitle(prop) to set the title
|
|
||||||
*/
|
|
||||||
title?: string;
|
|
||||||
operation: 'increment' | 'set';
|
|
||||||
value: number;
|
|
||||||
prop: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type TaskResult = {
|
|
||||||
propId: string;
|
propId: string;
|
||||||
targetIds: string[];
|
targetIds: string[];
|
||||||
scope: any;
|
|
||||||
popScope?: any;
|
|
||||||
pushScope?: any;
|
|
||||||
mutations: Mutation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class PartialTaskResult {
|
|
||||||
scope: any;
|
scope: any;
|
||||||
// Consume pushed changes from the local scope, every change pushed must be popped later
|
// Consume pushed changes from the local scope, every change pushed must be popped later
|
||||||
popScope?: any;
|
popScope?: any;
|
||||||
@@ -92,7 +52,9 @@ class PartialTaskResult {
|
|||||||
// properties can be found on variable.previous
|
// properties can be found on variable.previous
|
||||||
pushScope?: any;
|
pushScope?: any;
|
||||||
mutations: Mutation[];
|
mutations: Mutation[];
|
||||||
constructor() {
|
constructor(propId: string, targetIds: string[]) {
|
||||||
|
this.propId = propId;
|
||||||
|
this.targetIds = targetIds;
|
||||||
this.mutations = [];
|
this.mutations = [];
|
||||||
this.scope = {};
|
this.scope = {};
|
||||||
}
|
}
|
||||||
@@ -307,8 +269,9 @@ export async function applyAction(action: Action, userInput?: any, simulate?: bo
|
|||||||
action._stepThrough = stepThrough;
|
action._stepThrough = stepThrough;
|
||||||
action._isSimulation = simulate;
|
action._isSimulation = simulate;
|
||||||
action.taskCount = 0;
|
action.taskCount = 0;
|
||||||
applyTask(action, {
|
const prop = await getSingleProperty(action.creatureId, action.rootPropId);
|
||||||
propId: action.rootPropId,
|
await applyTask(action, {
|
||||||
|
prop,
|
||||||
targetIds: action.targetIds || [],
|
targetIds: action.targetIds || [],
|
||||||
}, userInput);
|
}, userInput);
|
||||||
return { action, userInput };
|
return { action, userInput };
|
||||||
@@ -322,10 +285,17 @@ async function applyTask(action: Action, task: Task, userInput?): Promise<void>
|
|||||||
if (action.taskCount > 100) throw 'Only 100 properties can be applied at once';
|
if (action.taskCount > 100) throw 'Only 100 properties can be applied at once';
|
||||||
|
|
||||||
if (task.subtaskFn) {
|
if (task.subtaskFn) {
|
||||||
await applySubtask[task.subtaskFn](task, action, userInput);
|
const result = new TaskResult(task.prop._id, task.targetIds);
|
||||||
|
action.results.push(result);
|
||||||
|
switch (task.subtaskFn) {
|
||||||
|
case 'damageProp':
|
||||||
|
return damageProp(task, action, result, userInput);
|
||||||
|
case 'consumeItemAsAmmo':
|
||||||
|
return consumeItemAsAmmo(task, action, result, userInput);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get property
|
// Get property
|
||||||
const prop = await getSingleProperty(action.creatureId, task.propId);
|
const prop = task.prop;
|
||||||
|
|
||||||
// Ensure the prop exists
|
// Ensure the prop exists
|
||||||
if (!prop) throw new Meteor.Error('Not found', 'Property could not be found');
|
if (!prop) throw new Meteor.Error('Not found', 'Property could not be found');
|
||||||
@@ -335,22 +305,19 @@ async function applyTask(action: Action, task: Task, userInput?): Promise<void>
|
|||||||
|
|
||||||
// Before triggers
|
// Before triggers
|
||||||
if (prop.triggerIds?.before?.length) {
|
if (prop.triggerIds?.before?.length) {
|
||||||
forEach(prop.triggerIds.before, triggerId => {
|
for (const triggerId of prop.triggerIds.before) {
|
||||||
applyTask(action, { propId: triggerId, targetIds: task.targetIds }, userInput);
|
const trigger = await getSingleProperty(action.creatureId, triggerId);
|
||||||
});
|
await applyTask(action, { prop: trigger, targetIds: task.targetIds }, userInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a result an push it to the action results, pass it to the apply function to modify
|
// Create a result an push it to the action results, pass it to the apply function to modify
|
||||||
const result = new PartialTaskResult();
|
const result = new TaskResult(task.prop._id, task.targetIds);
|
||||||
result.scope[`#${prop.type}`] = prop;
|
result.scope[`#${prop.type}`] = prop;
|
||||||
action.results.push({
|
action.results.push(result);
|
||||||
...result,
|
|
||||||
propId: task.propId,
|
|
||||||
targetIds: task.targetIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply the property
|
// Apply the property
|
||||||
await applyPropertyByType[prop.type]?.(prop, task, action, result, userInput);
|
return applyPropertyByType[prop.type]?.(task, action, result, userInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,10 +346,10 @@ function getPropertyTitle(prop) {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function applyChildren(action: Action, prop, targetIds, userInput) {
|
async function applyChildren(action: Action, prop, targetIds, userInput) {
|
||||||
const children = await getPropertyChildren(action.creatureId, prop._id);
|
const children = await getPropertyChildren(action.creatureId, prop);
|
||||||
// Push the child tasks and related triggers to the stack
|
// Push the child tasks and related triggers to the stack
|
||||||
for (const childProp of children) {
|
for (const childProp of children) {
|
||||||
await applyTask(action, { propId: childProp._id, targetIds }, userInput);
|
await applyTask(action, { prop: childProp, targetIds }, userInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,14 +362,16 @@ async function applyChildren(action: Action, prop, targetIds, userInput) {
|
|||||||
async function applyAfterChildrenTriggers(action: Action, prop, targetIds, userInput) {
|
async function applyAfterChildrenTriggers(action: Action, prop, targetIds, userInput) {
|
||||||
if (!prop.triggerIds?.afterChildren) return;
|
if (!prop.triggerIds?.afterChildren) return;
|
||||||
for (const triggerId of prop.triggerIds.afterChildren) {
|
for (const triggerId of prop.triggerIds.afterChildren) {
|
||||||
await applyTask(action, { propId: triggerId, targetIds }, userInput);
|
const trigger = await getSingleProperty(action.creatureId, triggerId);
|
||||||
|
await applyTask(action, { prop: trigger, targetIds }, userInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyAfterTriggers(action: Action, prop, targetIds, userInput) {
|
async function applyAfterTriggers(action: Action, prop, targetIds, userInput) {
|
||||||
if (!prop.triggerIds?.after) return;
|
if (!prop.triggerIds?.after) return;
|
||||||
for (const triggerId of prop.triggerIds.after) {
|
for (const triggerId of prop.triggerIds.after) {
|
||||||
await applyTask(action, { propId: triggerId, targetIds }, userInput);
|
const trigger = await getSingleProperty(action.creatureId, triggerId);
|
||||||
|
await applyTask(action, { prop: trigger, targetIds }, userInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +416,7 @@ async function applyAfterTasksSkipChildren(action: Action, prop, targetIds, user
|
|||||||
*/
|
*/
|
||||||
async function applyAfterPropTasksForSingleChild(action: Action, prop, childProp, targetIds, userInput) {
|
async function applyAfterPropTasksForSingleChild(action: Action, prop, childProp, targetIds, userInput) {
|
||||||
await applyAfterTriggers(action, prop, targetIds, userInput);
|
await applyAfterTriggers(action, prop, targetIds, userInput);
|
||||||
await applyTask(action, { propId: childProp._id, targetIds }, userInput);
|
await applyTask(action, { prop: childProp, targetIds }, userInput);
|
||||||
await applyAfterChildrenTriggers(action, prop, targetIds, userInput);
|
await applyAfterChildrenTriggers(action, prop, targetIds, userInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,7 +432,8 @@ async function applyTriggers(action: Action, prop, targetIds: string[], triggerP
|
|||||||
const triggerIds = get(prop?.triggers, triggerPath);
|
const triggerIds = get(prop?.triggers, triggerPath);
|
||||||
if (!triggerIds) return;
|
if (!triggerIds) return;
|
||||||
for (const triggerId of triggerIds) {
|
for (const triggerId of triggerIds) {
|
||||||
await applyTask(action, { propId: triggerId, targetIds }, userInput);
|
const trigger = await getSingleProperty(action.creatureId, triggerId);
|
||||||
|
await applyTask(action, { prop: trigger, targetIds }, userInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,9 +510,11 @@ export function getEffectiveActionScope(action: Action) {
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const applyPropertyByType = {
|
const applyPropertyByType = {
|
||||||
|
|
||||||
async action(prop, task: PropTask, action: Action, result: PartialTaskResult, userInput): Promise<void> {
|
async action(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
|
const prop = task.prop;
|
||||||
const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||||
|
|
||||||
//Log the name and summary, check that the property has enough resources to fire
|
//Log the name and summary, check that the property has enough resources to fire
|
||||||
@@ -578,13 +550,13 @@ const applyPropertyByType = {
|
|||||||
const scope = getEffectiveActionScope(action);
|
const scope = getEffectiveActionScope(action);
|
||||||
const statToDamage = getFromScope(att.variableName, scope);
|
const statToDamage = getFromScope(att.variableName, scope);
|
||||||
await applyTask(action, {
|
await applyTask(action, {
|
||||||
propId: task.propId,
|
prop,
|
||||||
targetIds: [action.creatureId],
|
targetIds: [action.creatureId],
|
||||||
subtaskFn: 'damageProp',
|
subtaskFn: 'damageProp',
|
||||||
params: {
|
params: {
|
||||||
operation: 'increment',
|
operation: 'increment',
|
||||||
value: +att.quantity?.value || 0,
|
value: +att.quantity?.value || 0,
|
||||||
prop: statToDamage,
|
targetProp: statToDamage,
|
||||||
},
|
},
|
||||||
}, userInput);
|
}, userInput);
|
||||||
}
|
}
|
||||||
@@ -607,13 +579,12 @@ const applyPropertyByType = {
|
|||||||
!isFinite(quantity)
|
!isFinite(quantity)
|
||||||
) continue;
|
) continue;
|
||||||
await applyTask(action, {
|
await applyTask(action, {
|
||||||
propId: item._id,
|
prop,
|
||||||
targetIds,
|
targetIds,
|
||||||
subtaskFn: 'consumeItemAsAmmo',
|
subtaskFn: 'consumeItemAsAmmo',
|
||||||
params: {
|
params: {
|
||||||
operation: 'increment',
|
|
||||||
value: quantity,
|
value: quantity,
|
||||||
prop: item,
|
item,
|
||||||
},
|
},
|
||||||
}, userInput);
|
}, userInput);
|
||||||
}
|
}
|
||||||
@@ -623,7 +594,8 @@ const applyPropertyByType = {
|
|||||||
return await applyDefaultAfterPropTasks(action, prop, targetIds, userInput);
|
return await applyDefaultAfterPropTasks(action, prop, targetIds, userInput);
|
||||||
},
|
},
|
||||||
|
|
||||||
async adjustment(prop, task: PropTask, action: Action, result: PartialTaskResult, userInput): Promise<void> {
|
async adjustment(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
|
const prop = task.prop;
|
||||||
const damageTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
const damageTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||||
|
|
||||||
if (damageTargetIds.length > 1) {
|
if (damageTargetIds.length > 1) {
|
||||||
@@ -660,39 +632,23 @@ const applyPropertyByType = {
|
|||||||
}, damageTargetIds);
|
}, damageTargetIds);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Set the scope properties
|
|
||||||
result.pushScope = {};
|
|
||||||
if (prop.operation === 'increment') {
|
|
||||||
if (value >= 0) {
|
|
||||||
result.pushScope['~damage'] = { value };
|
|
||||||
} else {
|
|
||||||
result.pushScope['~healing'] = { value: -value };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.pushScope['~set'] = { value };
|
|
||||||
}
|
|
||||||
// Store which property we're targeting
|
|
||||||
if (targetId === action.creatureId) {
|
|
||||||
result.pushScope['~attributeDamaged'] = { _propId: stat._id };
|
|
||||||
} else {
|
|
||||||
result.pushScope['~attributeDamaged'] = stat;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTask(action, {
|
applyTask(action, {
|
||||||
propId: task.propId,
|
prop,
|
||||||
targetIds: damageTargetIds,
|
targetIds: damageTargetIds,
|
||||||
subtaskFn: 'damageProp',
|
subtaskFn: 'damageProp',
|
||||||
params: {
|
params: {
|
||||||
title: getPropertyTitle(prop),
|
title: getPropertyTitle(prop),
|
||||||
operation: prop.operation,
|
operation: prop.operation,
|
||||||
value,
|
value,
|
||||||
prop: stat,
|
targetProp: stat,
|
||||||
},
|
},
|
||||||
}, userInput);
|
}, userInput);
|
||||||
return applyDefaultAfterPropTasks(action, prop, damageTargetIds, userInput);
|
return applyDefaultAfterPropTasks(action, prop, damageTargetIds, userInput);
|
||||||
},
|
},
|
||||||
|
|
||||||
async branch(prop, task: PropTask, action: Action, result: PartialTaskResult, userInput): Promise<void> {
|
async branch(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
|
const prop = task.prop;
|
||||||
const targets = task.targetIds;
|
const targets = task.targetIds;
|
||||||
|
|
||||||
switch (prop.branchType) {
|
switch (prop.branchType) {
|
||||||
@@ -705,7 +661,7 @@ const applyPropertyByType = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'index': {
|
case 'index': {
|
||||||
const children = await getPropertyChildren(action.creatureId, prop._id);
|
const children = await getPropertyChildren(action.creatureId, prop);
|
||||||
if (!children.length) {
|
if (!children.length) {
|
||||||
return applyAfterTasksSkipChildren(action, prop, targets, userInput);
|
return applyAfterTasksSkipChildren(action, prop, targets, userInput);
|
||||||
}
|
}
|
||||||
@@ -776,7 +732,7 @@ const applyPropertyByType = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'random': {
|
case 'random': {
|
||||||
const children = await getPropertyChildren(action.creatureId, prop._id);
|
const children = await getPropertyChildren(action.creatureId, prop);
|
||||||
if (children.length) {
|
if (children.length) {
|
||||||
const index = rollDice(1, children.length)[0];
|
const index = rollDice(1, children.length)[0];
|
||||||
const child = children[index - 1];
|
const child = children[index - 1];
|
||||||
@@ -800,7 +756,7 @@ const applyPropertyByType = {
|
|||||||
if (!action._isSimulation && !userInput?.[prop._id]) {
|
if (!action._isSimulation && !userInput?.[prop._id]) {
|
||||||
throw 'User input was required for this step'
|
throw 'User input was required for this step'
|
||||||
}
|
}
|
||||||
const children = await getPropertyChildren(action.creatureId, prop._id);
|
const children = await getPropertyChildren(action.creatureId, prop);
|
||||||
if (!children.length) {
|
if (!children.length) {
|
||||||
return applyAfterTasksSkipChildren(action, prop, targets, userInput);
|
return applyAfterTasksSkipChildren(action, prop, targets, userInput);
|
||||||
}
|
}
|
||||||
@@ -813,15 +769,16 @@ const applyPropertyByType = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async folder(prop, task: PropTask, action: Action, userInput): Promise<void> {
|
async folder(task: PropTask, action: Action, userInput): Promise<void> {
|
||||||
|
const prop = task.prop;
|
||||||
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
||||||
},
|
},
|
||||||
|
|
||||||
async note(prop, task: PropTask, action: Action, userInput): Promise<void> {
|
async note(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
const result = new PartialTaskResult();
|
const prop = task.prop;
|
||||||
|
|
||||||
let contents: LogContent[] | undefined = undefined;
|
let contents: LogContent[] | undefined = undefined;
|
||||||
const logContent = { name: prop.name, value: undefined };
|
const logContent: LogContent = {};
|
||||||
|
if (prop.name) logContent.name = prop.name;
|
||||||
if (prop.summary?.text) {
|
if (prop.summary?.text) {
|
||||||
await recalculateInlineCalculations(prop.summary, action);
|
await recalculateInlineCalculations(prop.summary, action);
|
||||||
logContent.value = prop.summary.value;
|
logContent.value = prop.summary.value;
|
||||||
@@ -845,9 +802,8 @@ const applyPropertyByType = {
|
|||||||
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
||||||
},
|
},
|
||||||
|
|
||||||
async roll(prop, task: PropTask, action: Action, userInput): Promise<void> {
|
async roll(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
const result = new PartialTaskResult();
|
const prop = task.prop;
|
||||||
|
|
||||||
// If there isn't a calculation, just apply the children instead
|
// If there isn't a calculation, just apply the children instead
|
||||||
if (!prop.roll?.calculation) {
|
if (!prop.roll?.calculation) {
|
||||||
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
||||||
@@ -897,115 +853,192 @@ const applyPropertyByType = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const applySubtask = {
|
|
||||||
async damageProp(task: DamagePropTask, action: Action, userInput): Promise<void> {
|
|
||||||
await applyTriggers(action, task.params.prop, [action.creatureId], 'damageProperty.before', userInput);
|
|
||||||
const result = new PartialTaskResult();
|
|
||||||
let { value } = task.params;
|
|
||||||
const { title, operation, silent, prop } = task.params;
|
|
||||||
|
|
||||||
// Get the user-mutable state from scope
|
// Sub tasks
|
||||||
const scope = getEffectiveActionScope(action);
|
|
||||||
result.popScope = {
|
|
||||||
'~damage': 1, '~healing': 1, '~set': 1, '~attributeDamaged': 1,
|
|
||||||
};
|
|
||||||
value = +value;
|
|
||||||
if (operation === 'increment') {
|
|
||||||
if (value >= 0) {
|
|
||||||
value = scope['~damage']?.value;
|
|
||||||
} else {
|
|
||||||
value = -scope['~healing']?.value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = scope['~set']?.value;
|
|
||||||
}
|
|
||||||
const targetPropId = scope['~attributeDamaged']?._propId;
|
|
||||||
|
|
||||||
// If there are no targets, just log the result that would apply and end
|
interface DamagePropTask extends BaseTask {
|
||||||
if (!task.targetIds?.length) {
|
subtaskFn: 'damageProp';
|
||||||
// Get the locally equivalent stat with the same variable name
|
params: {
|
||||||
const statName = getPropertyTitle(prop);
|
/**
|
||||||
result.appendLog({
|
* Use getPropertyTitle(prop) to set the title
|
||||||
name: title,
|
*/
|
||||||
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
|
title?: string;
|
||||||
` ${value}`,
|
operation: 'increment' | 'set';
|
||||||
inline: true,
|
value: number;
|
||||||
silenced: silent,
|
targetProp: any;
|
||||||
}, task.targetIds);
|
};
|
||||||
return saveResult(action, prop, result, task);
|
}
|
||||||
}
|
|
||||||
|
async function damageProp(task: DamagePropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
if (task.targetIds.length !== 1) {
|
const prop = task.prop;
|
||||||
throw 'This subtask can only be called on a single target';
|
|
||||||
}
|
if (task.targetIds.length > 1) {
|
||||||
const targetId = task.targetIds[0];
|
throw 'This subtask can only be called on a single target';
|
||||||
|
}
|
||||||
let damage, newValue, increment;
|
const targetId = task.targetIds[0];
|
||||||
const targetProp = await getSingleProperty(targetId, targetPropId);
|
|
||||||
|
let { value } = task.params;
|
||||||
if (!targetProp) return saveResult(action, prop, result, task);
|
const { title, operation } = task.params;
|
||||||
|
let targetProp = task.params.targetProp;
|
||||||
if (operation === 'set') {
|
|
||||||
const total = targetProp.total || 0;
|
// Set the scope properties
|
||||||
// Set represents what we want the value to be after damage
|
result.pushScope = {};
|
||||||
// So we need the actual damage to get to that value
|
if (prop.operation === 'increment') {
|
||||||
damage = total - value;
|
if (value >= 0) {
|
||||||
// Damage can't exceed total value
|
result.pushScope['~damage'] = { value };
|
||||||
if (damage > total && !targetProp.ignoreLowerLimit) damage = total;
|
} else {
|
||||||
// Damage must be positive
|
result.pushScope['~healing'] = { value: -value };
|
||||||
if (damage < 0 && !targetProp.ignoreUpperLimit) damage = 0;
|
}
|
||||||
newValue = targetProp.total - damage;
|
} else {
|
||||||
// Write the results
|
result.pushScope['~set'] = { value };
|
||||||
result.mutations.push({
|
}
|
||||||
targetIds: [targetId],
|
// Store which property we're targeting
|
||||||
updates: [{
|
if (targetId === action.creatureId) {
|
||||||
propId: targetProp._id,
|
result.pushScope['~attributeDamaged'] = { _propId: targetProp._id };
|
||||||
set: { damage, value: newValue },
|
} else {
|
||||||
type: targetProp.type,
|
result.pushScope['~attributeDamaged'] = targetProp;
|
||||||
}],
|
}
|
||||||
contents: [{
|
|
||||||
name: title,
|
// Run the before triggers which may change scope properties
|
||||||
value: `${getPropertyTitle(targetProp)} set to ${value}`,
|
await applyTriggers(action, targetProp, [action.creatureId], 'damageProperty.before', userInput);
|
||||||
inline: true,
|
|
||||||
silenced: task.params.silent,
|
// Refetch the scope properties
|
||||||
}]
|
const scope = getEffectiveActionScope(action);
|
||||||
});
|
result.popScope = {
|
||||||
} else if (operation === 'increment') {
|
'~damage': 1, '~healing': 1, '~set': 1, '~attributeDamaged': 1,
|
||||||
const currentValue = targetProp.value || 0;
|
};
|
||||||
const currentDamage = targetProp.damage || 0;
|
value = +value;
|
||||||
increment = value;
|
if (operation === 'increment') {
|
||||||
// Can't increase damage above the remaining value
|
if (value >= 0) {
|
||||||
if (increment > currentValue && !targetProp.ignoreLowerLimit) increment = currentValue;
|
value = scope['~damage']?.value;
|
||||||
// Can't decrease damage below zero
|
} else {
|
||||||
if (-increment > currentDamage && !targetProp.ignoreUpperLimit) increment = -currentDamage;
|
value = -scope['~healing']?.value;
|
||||||
damage = currentDamage + increment;
|
}
|
||||||
newValue = targetProp.total - damage;
|
} else {
|
||||||
// Write the results
|
value = scope['~set']?.value;
|
||||||
result.mutations.push({
|
}
|
||||||
targetIds: [targetId],
|
const targetPropId = scope['~attributeDamaged']?._propId;
|
||||||
updates: [{
|
|
||||||
propId: targetProp._id,
|
// If there are no targets, just log the result that would apply and end
|
||||||
inc: { damage: increment, value: -increment },
|
if (!task.targetIds?.length) {
|
||||||
type: targetProp.type,
|
// Get the locally equivalent stat with the same variable name
|
||||||
}],
|
const statName = getPropertyTitle(targetProp);
|
||||||
contents: [{
|
result.appendLog({
|
||||||
name: 'Attribute damage',
|
name: title,
|
||||||
value: `${getPropertyTitle(targetProp)} ${value}`,
|
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
|
||||||
inline: true,
|
` ${value}`,
|
||||||
silenced: silent,
|
inline: true,
|
||||||
}]
|
silenced: prop.silent,
|
||||||
});
|
}, task.targetIds);
|
||||||
}
|
}
|
||||||
saveResult(action, prop, result, task);
|
|
||||||
await applyTriggers(action, prop, [action.creatureId], 'damageProperty.after', userInput);
|
let damage, newValue, increment;
|
||||||
},
|
targetProp = await getSingleProperty(targetId, targetPropId);
|
||||||
|
|
||||||
async consumeItemAsAmmo(task: ItemAsAmmoTask, action: Action, userInput): Promise<void> {
|
if (!targetProp) return;
|
||||||
await applyTriggers(action, task.params.prop, [action.creatureId], 'ammo.before', userInput);
|
|
||||||
const result = new PartialTaskResult();
|
if (operation === 'set') {
|
||||||
|
const total = targetProp.total || 0;
|
||||||
//TODO
|
// Set represents what we want the value to be after damage
|
||||||
|
// So we need the actual damage to get to that value
|
||||||
return applyTriggers(action, prop, [action.creatureId], 'ammo.after', userInput);
|
damage = total - value;
|
||||||
},
|
// Damage can't exceed total value
|
||||||
|
if (damage > total && !targetProp.ignoreLowerLimit) damage = total;
|
||||||
|
// Damage must be positive
|
||||||
|
if (damage < 0 && !targetProp.ignoreUpperLimit) damage = 0;
|
||||||
|
newValue = targetProp.total - damage;
|
||||||
|
// Write the results
|
||||||
|
result.mutations.push({
|
||||||
|
targetIds: [targetId],
|
||||||
|
updates: [{
|
||||||
|
propId: targetProp._id,
|
||||||
|
set: { damage, value: newValue },
|
||||||
|
type: targetProp.type,
|
||||||
|
}],
|
||||||
|
contents: [{
|
||||||
|
name: title,
|
||||||
|
value: `${getPropertyTitle(targetProp)} set to ${value}`,
|
||||||
|
inline: true,
|
||||||
|
silenced: prop.silent,
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else if (operation === 'increment') {
|
||||||
|
const currentValue = targetProp.value || 0;
|
||||||
|
const currentDamage = targetProp.damage || 0;
|
||||||
|
increment = value;
|
||||||
|
// Can't increase damage above the remaining value
|
||||||
|
if (increment > currentValue && !targetProp.ignoreLowerLimit) increment = currentValue;
|
||||||
|
// Can't decrease damage below zero
|
||||||
|
if (-increment > currentDamage && !targetProp.ignoreUpperLimit) increment = -currentDamage;
|
||||||
|
damage = currentDamage + increment;
|
||||||
|
newValue = targetProp.total - damage;
|
||||||
|
// Write the results
|
||||||
|
result.mutations.push({
|
||||||
|
targetIds: [targetId],
|
||||||
|
updates: [{
|
||||||
|
propId: targetProp._id,
|
||||||
|
inc: { damage: increment, value: -increment },
|
||||||
|
type: targetProp.type,
|
||||||
|
}],
|
||||||
|
contents: [{
|
||||||
|
name: 'Attribute damage',
|
||||||
|
value: `${getPropertyTitle(targetProp)} ${value}`,
|
||||||
|
inline: true,
|
||||||
|
silenced: prop.silent,
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await applyTriggers(action, prop, [action.creatureId], 'damageProperty.after', userInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ItemAsAmmoTask extends BaseTask {
|
||||||
|
subtaskFn: 'consumeItemAsAmmo';
|
||||||
|
params: {
|
||||||
|
value: number;
|
||||||
|
item: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function consumeItemAsAmmo(task: ItemAsAmmoTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||||
|
const prop = task.prop;
|
||||||
|
let { value, item } = task.params;
|
||||||
|
|
||||||
|
if (item.type !== 'item') throw 'Must use an item as ammo';
|
||||||
|
|
||||||
|
// Store the ammo item and value in the scope
|
||||||
|
result.scope[`#ammo`] = { propId: item._id };
|
||||||
|
result.pushScope = { ['~ammoConsumed']: { value } };
|
||||||
|
|
||||||
|
// Apply the before triggers
|
||||||
|
await applyTriggers(action, item, [action.creatureId], 'ammo.before', userInput);
|
||||||
|
|
||||||
|
// Refetch the scope properties
|
||||||
|
const scope = getEffectiveActionScope(action);
|
||||||
|
result.popScope = {
|
||||||
|
'~ammoConsumed': 1,
|
||||||
|
};
|
||||||
|
value = scope['~ammoConsumed']?.value || 0;
|
||||||
|
|
||||||
|
const itemChildren = await getPropertyChildren(action.creatureId, item);
|
||||||
|
|
||||||
|
// Do the quantity adjustment
|
||||||
|
// Check if property has quantity
|
||||||
|
result.mutations.push({
|
||||||
|
targetIds: task.targetIds,
|
||||||
|
updates: [{
|
||||||
|
propId: item._id,
|
||||||
|
inc: { quantity: -value },
|
||||||
|
type: 'item',
|
||||||
|
}],
|
||||||
|
// Log the item name as a heading if it has child properties to apply
|
||||||
|
contents: itemChildren.length ? [{
|
||||||
|
name: getPropertyTitle(item) || 'Ammo',
|
||||||
|
inline: false,
|
||||||
|
silenced: prop.silent,
|
||||||
|
}] : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
await applyTriggers(action, item, [action.creatureId], 'ammo.after', userInput);
|
||||||
|
return applyDefaultAfterPropTasks(action, item, task.targetIds, userInput);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn';
|
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
|
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
|
||||||
import Actions, { Action, Update, LogContent, runActionWork, propTasks } from '/imports/api/engine/actions/Actions';
|
import Actions, { Action, Update, LogContent, applyAction } from '/imports/api/engine/actions/Actions';
|
||||||
import computeCreature from '/imports/api/engine/computeCreature';
|
import computeCreature from '/imports/api/engine/computeCreature';
|
||||||
import { loadCreature } from '/imports/api/engine/loadCreatures';
|
import { loadCreature } from '/imports/api/engine/loadCreatures';
|
||||||
|
|
||||||
@@ -42,6 +42,13 @@ describe('Interrupt action system', function () {
|
|||||||
[{ value: 'Note 1 summary. 1 + 1 = 2' }]
|
[{ value: 'Note 1 summary. 1 + 1 = 2' }]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('Applies children of folders', async function () {
|
||||||
|
const action = await runActionById(folderId);
|
||||||
|
assert.deepEqual(
|
||||||
|
allLogContent(action),
|
||||||
|
[{ value: 'child of folder' }]
|
||||||
|
);
|
||||||
|
});
|
||||||
it('Applies the children of if branches', async function () {
|
it('Applies the children of if branches', async function () {
|
||||||
let action = await runActionById(ifTruthyBranchId);
|
let action = await runActionById(ifTruthyBranchId);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
@@ -62,12 +69,8 @@ describe('Interrupt action system', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('Halts execution of choice branches', async function () {
|
it('Halts execution of choice branches', async function () {
|
||||||
|
throw 'not implemented yet';
|
||||||
const action = await runActionById(choiceBranchId);
|
const action = await runActionById(choiceBranchId);
|
||||||
assert.exists(action.userInputNeeded);
|
|
||||||
assert.deepEqual(
|
|
||||||
allLogContent(action),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('Applies adjustments', async function () {
|
it('Applies adjustments', async function () {
|
||||||
let action = await runActionById(adjustmentSetId);
|
let action = await runActionById(adjustmentSetId);
|
||||||
@@ -97,7 +100,8 @@ describe('Interrupt action system', function () {
|
|||||||
{
|
{
|
||||||
name: 'New Roll',
|
name: 'New Roll',
|
||||||
value: '7d1 [1, 1, 1, 1, 1, 1, 1] + 9\n**16**',
|
value: '7d1 [1, 1, 1, 1, 1, 1, 1] + 9\n**16**',
|
||||||
inline: true
|
inline: true,
|
||||||
|
silenced: undefined,
|
||||||
}, {
|
}, {
|
||||||
value: 'rollVar: 16'
|
value: 'rollVar: 16'
|
||||||
}
|
}
|
||||||
@@ -109,8 +113,9 @@ function createAction(prop, targetIds?) {
|
|||||||
const action: Action = {
|
const action: Action = {
|
||||||
creatureId: prop.ancestors[0].id,
|
creatureId: prop.ancestors[0].id,
|
||||||
rootPropId: prop._id,
|
rootPropId: prop._id,
|
||||||
taskQueue: [{ propId: prop._id, targetIds }],
|
|
||||||
results: [],
|
results: [],
|
||||||
|
taskCount: 0,
|
||||||
|
targetIds,
|
||||||
};
|
};
|
||||||
return Actions.insertAsync(action);
|
return Actions.insertAsync(action);
|
||||||
}
|
}
|
||||||
@@ -118,9 +123,9 @@ function createAction(prop, targetIds?) {
|
|||||||
async function runActionById(propId) {
|
async function runActionById(propId) {
|
||||||
const prop = await CreatureProperties.findOneAsync(propId);
|
const prop = await CreatureProperties.findOneAsync(propId);
|
||||||
const actionId = await createAction(prop);
|
const actionId = await createAction(prop);
|
||||||
await runActionWork(actionId);
|
|
||||||
const action = await Actions.findOneAsync(actionId);
|
const action = await Actions.findOneAsync(actionId);
|
||||||
if (!action) throw 'Action is expected to exist'
|
if (!action) throw 'Action is expected to exist';
|
||||||
|
await applyAction(action);
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +153,7 @@ function allLogContent(action: Action) {
|
|||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
let note1Id, ifTruthyBranchId, ifFalsyBranchId, indexBranchId, choiceBranchId, adjustedStatId,
|
let note1Id, folderId, ifTruthyBranchId, ifFalsyBranchId, indexBranchId, choiceBranchId, adjustedStatId,
|
||||||
adjustmentIncrementId, adjustmentSetId, rollId;
|
adjustmentIncrementId, adjustmentSetId, rollId;
|
||||||
|
|
||||||
const propForest = [
|
const propForest = [
|
||||||
@@ -160,6 +165,12 @@ const propForest = [
|
|||||||
text: 'Note 1 summary. 1 + 1 = {1 + 1}'
|
text: 'Note 1 summary. 1 + 1 = {1 + 1}'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Apply a folder with a note inside
|
||||||
|
{
|
||||||
|
_id: folderId = Random.id(),
|
||||||
|
type: 'folder',
|
||||||
|
children: [{ type: 'note', summary: { text: 'child of folder' } }],
|
||||||
|
},
|
||||||
// Apply an if branch with a truthy condition
|
// Apply an if branch with a truthy condition
|
||||||
{
|
{
|
||||||
_id: ifTruthyBranchId = Random.id(),
|
_id: ifTruthyBranchId = Random.id(),
|
||||||
|
|||||||
@@ -189,15 +189,22 @@ export function getPropertyDecendants(creatureId, propertyId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPropertyChildren(creatureId, propertyId) {
|
/**
|
||||||
const property = getSingleProperty(creatureId, propertyId);
|
* @param {string} creatureId Creature ID
|
||||||
|
* @param {string | any} property prop or prop ID to get children of
|
||||||
|
* @returns {any[]} An array of child properties in tree order
|
||||||
|
*/
|
||||||
|
export function getPropertyChildren(creatureId, property) {
|
||||||
|
if (typeof property === 'string') {
|
||||||
|
property = getSingleProperty(creatureId, property);
|
||||||
|
}
|
||||||
if (!property) return [];
|
if (!property) return [];
|
||||||
// This propertyId will always appear in the parent of the children
|
// This propertyId will always appear in the parent of the children
|
||||||
if (loadedCreatures.has(creatureId)) {
|
if (loadedCreatures.has(creatureId)) {
|
||||||
const creature = loadedCreatures.get(creatureId);
|
const creature = loadedCreatures.get(creatureId);
|
||||||
const props = [];
|
const props = [];
|
||||||
for (const prop of creature.properties.values()) {
|
for (const prop of creature.properties.values()) {
|
||||||
if (prop.parent?.id === propertyId) {
|
if (prop.parent?.id === property._id) {
|
||||||
props.push(prop);
|
props.push(prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +212,7 @@ export function getPropertyChildren(creatureId, propertyId) {
|
|||||||
return cloneProps.sort((a, b) => a.order - b.order);
|
return cloneProps.sort((a, b) => a.order - b.order);
|
||||||
} else {
|
} else {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
'parent.id': propertyId,
|
'parent.id': property._id,
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
}, {
|
}, {
|
||||||
sort: { order: 1 },
|
sort: { order: 1 },
|
||||||
|
|||||||
Reference in New Issue
Block a user