Finished move to 3 tier action methods

This commit is contained in:
ThaumRystra
2023-12-15 12:25:32 +02:00
parent 99bf5fd832
commit 862c2ceb99
3 changed files with 276 additions and 225 deletions

View File

@@ -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);
} }

View File

@@ -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(),

View File

@@ -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 },