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 { getPropertyName } from '/imports/constants/PROPERTIES';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||
import { use } from 'chai';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
@@ -31,55 +29,17 @@ export interface Action {
|
||||
type Task = PropTask | DamagePropTask | ItemAsAmmoTask;
|
||||
|
||||
interface BaseTask {
|
||||
propId: string;
|
||||
prop: { [key: string]: any };
|
||||
targetIds: string[];
|
||||
}
|
||||
|
||||
interface PropTask extends BaseTask {
|
||||
step?: number,
|
||||
subtaskFn?: undefined,
|
||||
beforeTriggersDone?: undefined | true;
|
||||
taskScope?: {
|
||||
[variableName: string]: { value: number },
|
||||
},
|
||||
}
|
||||
|
||||
interface DamagePropTask extends BaseTask {
|
||||
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 = {
|
||||
class TaskResult {
|
||||
propId: string;
|
||||
targetIds: string[];
|
||||
scope: any;
|
||||
popScope?: any;
|
||||
pushScope?: any;
|
||||
mutations: Mutation[];
|
||||
}
|
||||
|
||||
class PartialTaskResult {
|
||||
scope: any;
|
||||
// Consume pushed changes from the local scope, every change pushed must be popped later
|
||||
popScope?: any;
|
||||
@@ -92,7 +52,9 @@ class PartialTaskResult {
|
||||
// properties can be found on variable.previous
|
||||
pushScope?: any;
|
||||
mutations: Mutation[];
|
||||
constructor() {
|
||||
constructor(propId: string, targetIds: string[]) {
|
||||
this.propId = propId;
|
||||
this.targetIds = targetIds;
|
||||
this.mutations = [];
|
||||
this.scope = {};
|
||||
}
|
||||
@@ -307,8 +269,9 @@ export async function applyAction(action: Action, userInput?: any, simulate?: bo
|
||||
action._stepThrough = stepThrough;
|
||||
action._isSimulation = simulate;
|
||||
action.taskCount = 0;
|
||||
applyTask(action, {
|
||||
propId: action.rootPropId,
|
||||
const prop = await getSingleProperty(action.creatureId, action.rootPropId);
|
||||
await applyTask(action, {
|
||||
prop,
|
||||
targetIds: action.targetIds || [],
|
||||
}, 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 (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 {
|
||||
// Get property
|
||||
const prop = await getSingleProperty(action.creatureId, task.propId);
|
||||
const prop = task.prop;
|
||||
|
||||
// Ensure the prop exists
|
||||
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
|
||||
if (prop.triggerIds?.before?.length) {
|
||||
forEach(prop.triggerIds.before, triggerId => {
|
||||
applyTask(action, { propId: triggerId, targetIds: task.targetIds }, userInput);
|
||||
});
|
||||
for (const triggerId of prop.triggerIds.before) {
|
||||
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
|
||||
const result = new PartialTaskResult();
|
||||
const result = new TaskResult(task.prop._id, task.targetIds);
|
||||
result.scope[`#${prop.type}`] = prop;
|
||||
action.results.push({
|
||||
...result,
|
||||
propId: task.propId,
|
||||
targetIds: task.targetIds,
|
||||
});
|
||||
action.results.push(result);
|
||||
|
||||
// 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
|
||||
*/
|
||||
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
|
||||
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) {
|
||||
if (!prop.triggerIds?.afterChildren) return;
|
||||
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) {
|
||||
if (!prop.triggerIds?.after) return;
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -463,7 +432,8 @@ async function applyTriggers(action: Action, prop, targetIds: string[], triggerP
|
||||
const triggerIds = get(prop?.triggers, triggerPath);
|
||||
if (!triggerIds) return;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
//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 statToDamage = getFromScope(att.variableName, scope);
|
||||
await applyTask(action, {
|
||||
propId: task.propId,
|
||||
prop,
|
||||
targetIds: [action.creatureId],
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
operation: 'increment',
|
||||
value: +att.quantity?.value || 0,
|
||||
prop: statToDamage,
|
||||
targetProp: statToDamage,
|
||||
},
|
||||
}, userInput);
|
||||
}
|
||||
@@ -607,13 +579,12 @@ const applyPropertyByType = {
|
||||
!isFinite(quantity)
|
||||
) continue;
|
||||
await applyTask(action, {
|
||||
propId: item._id,
|
||||
prop,
|
||||
targetIds,
|
||||
subtaskFn: 'consumeItemAsAmmo',
|
||||
params: {
|
||||
operation: 'increment',
|
||||
value: quantity,
|
||||
prop: item,
|
||||
item,
|
||||
},
|
||||
}, userInput);
|
||||
}
|
||||
@@ -623,7 +594,8 @@ const applyPropertyByType = {
|
||||
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;
|
||||
|
||||
if (damageTargetIds.length > 1) {
|
||||
@@ -660,39 +632,23 @@ const applyPropertyByType = {
|
||||
}, damageTargetIds);
|
||||
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, {
|
||||
propId: task.propId,
|
||||
prop,
|
||||
targetIds: damageTargetIds,
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
title: getPropertyTitle(prop),
|
||||
operation: prop.operation,
|
||||
value,
|
||||
prop: stat,
|
||||
targetProp: stat,
|
||||
},
|
||||
}, 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;
|
||||
|
||||
switch (prop.branchType) {
|
||||
@@ -705,7 +661,7 @@ const applyPropertyByType = {
|
||||
}
|
||||
}
|
||||
case 'index': {
|
||||
const children = await getPropertyChildren(action.creatureId, prop._id);
|
||||
const children = await getPropertyChildren(action.creatureId, prop);
|
||||
if (!children.length) {
|
||||
return applyAfterTasksSkipChildren(action, prop, targets, userInput);
|
||||
}
|
||||
@@ -776,7 +732,7 @@ const applyPropertyByType = {
|
||||
}
|
||||
}
|
||||
case 'random': {
|
||||
const children = await getPropertyChildren(action.creatureId, prop._id);
|
||||
const children = await getPropertyChildren(action.creatureId, prop);
|
||||
if (children.length) {
|
||||
const index = rollDice(1, children.length)[0];
|
||||
const child = children[index - 1];
|
||||
@@ -800,7 +756,7 @@ const applyPropertyByType = {
|
||||
if (!action._isSimulation && !userInput?.[prop._id]) {
|
||||
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) {
|
||||
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);
|
||||
},
|
||||
|
||||
async note(prop, task: PropTask, action: Action, userInput): Promise<void> {
|
||||
const result = new PartialTaskResult();
|
||||
|
||||
async note(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||
const prop = task.prop;
|
||||
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) {
|
||||
await recalculateInlineCalculations(prop.summary, action);
|
||||
logContent.value = prop.summary.value;
|
||||
@@ -845,9 +802,8 @@ const applyPropertyByType = {
|
||||
return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput);
|
||||
},
|
||||
|
||||
async roll(prop, task: PropTask, action: Action, userInput): Promise<void> {
|
||||
const result = new PartialTaskResult();
|
||||
|
||||
async roll(task: PropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||
const prop = task.prop;
|
||||
// If there isn't a calculation, just apply the children instead
|
||||
if (!prop.roll?.calculation) {
|
||||
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
|
||||
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;
|
||||
// Sub tasks
|
||||
|
||||
// If there are no targets, just log the result that would apply and end
|
||||
if (!task.targetIds?.length) {
|
||||
// Get the locally equivalent stat with the same variable name
|
||||
const statName = getPropertyTitle(prop);
|
||||
result.appendLog({
|
||||
name: title,
|
||||
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
inline: true,
|
||||
silenced: silent,
|
||||
}, task.targetIds);
|
||||
return saveResult(action, prop, result, task);
|
||||
}
|
||||
|
||||
if (task.targetIds.length !== 1) {
|
||||
throw 'This subtask can only be called on a single target';
|
||||
}
|
||||
const targetId = task.targetIds[0];
|
||||
|
||||
let damage, newValue, increment;
|
||||
const targetProp = await getSingleProperty(targetId, targetPropId);
|
||||
|
||||
if (!targetProp) return saveResult(action, prop, result, task);
|
||||
|
||||
if (operation === 'set') {
|
||||
const total = targetProp.total || 0;
|
||||
// Set represents what we want the value to be after damage
|
||||
// So we need the actual damage to get to that value
|
||||
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: task.params.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: silent,
|
||||
}]
|
||||
});
|
||||
}
|
||||
saveResult(action, prop, result, task);
|
||||
await applyTriggers(action, prop, [action.creatureId], 'damageProperty.after', userInput);
|
||||
},
|
||||
|
||||
async consumeItemAsAmmo(task: ItemAsAmmoTask, action: Action, userInput): Promise<void> {
|
||||
await applyTriggers(action, task.params.prop, [action.creatureId], 'ammo.before', userInput);
|
||||
const result = new PartialTaskResult();
|
||||
|
||||
//TODO
|
||||
|
||||
return applyTriggers(action, prop, [action.creatureId], 'ammo.after', userInput);
|
||||
},
|
||||
interface DamagePropTask extends BaseTask {
|
||||
subtaskFn: 'damageProp';
|
||||
params: {
|
||||
/**
|
||||
* Use getPropertyTitle(prop) to set the title
|
||||
*/
|
||||
title?: string;
|
||||
operation: 'increment' | 'set';
|
||||
value: number;
|
||||
targetProp: any;
|
||||
};
|
||||
}
|
||||
|
||||
async function damageProp(task: DamagePropTask, action: Action, result: TaskResult, userInput): Promise<void> {
|
||||
const prop = task.prop;
|
||||
|
||||
if (task.targetIds.length > 1) {
|
||||
throw 'This subtask can only be called on a single target';
|
||||
}
|
||||
const targetId = task.targetIds[0];
|
||||
|
||||
let { value } = task.params;
|
||||
const { title, operation } = task.params;
|
||||
let targetProp = task.params.targetProp;
|
||||
|
||||
// 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: targetProp._id };
|
||||
} else {
|
||||
result.pushScope['~attributeDamaged'] = targetProp;
|
||||
}
|
||||
|
||||
// Run the before triggers which may change scope properties
|
||||
await applyTriggers(action, targetProp, [action.creatureId], 'damageProperty.before', userInput);
|
||||
|
||||
// Refetch the scope properties
|
||||
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
|
||||
if (!task.targetIds?.length) {
|
||||
// Get the locally equivalent stat with the same variable name
|
||||
const statName = getPropertyTitle(targetProp);
|
||||
result.appendLog({
|
||||
name: title,
|
||||
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
inline: true,
|
||||
silenced: prop.silent,
|
||||
}, task.targetIds);
|
||||
}
|
||||
|
||||
let damage, newValue, increment;
|
||||
targetProp = await getSingleProperty(targetId, targetPropId);
|
||||
|
||||
if (!targetProp) return;
|
||||
|
||||
if (operation === 'set') {
|
||||
const total = targetProp.total || 0;
|
||||
// Set represents what we want the value to be after damage
|
||||
// So we need the actual damage to get to that value
|
||||
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 Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
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 { loadCreature } from '/imports/api/engine/loadCreatures';
|
||||
|
||||
@@ -42,6 +42,13 @@ describe('Interrupt action system', function () {
|
||||
[{ 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 () {
|
||||
let action = await runActionById(ifTruthyBranchId);
|
||||
assert.deepEqual(
|
||||
@@ -62,12 +69,8 @@ describe('Interrupt action system', function () {
|
||||
);
|
||||
});
|
||||
it('Halts execution of choice branches', async function () {
|
||||
throw 'not implemented yet';
|
||||
const action = await runActionById(choiceBranchId);
|
||||
assert.exists(action.userInputNeeded);
|
||||
assert.deepEqual(
|
||||
allLogContent(action),
|
||||
[]
|
||||
);
|
||||
});
|
||||
it('Applies adjustments', async function () {
|
||||
let action = await runActionById(adjustmentSetId);
|
||||
@@ -97,7 +100,8 @@ describe('Interrupt action system', function () {
|
||||
{
|
||||
name: 'New Roll',
|
||||
value: '7d1 [1, 1, 1, 1, 1, 1, 1] + 9\n**16**',
|
||||
inline: true
|
||||
inline: true,
|
||||
silenced: undefined,
|
||||
}, {
|
||||
value: 'rollVar: 16'
|
||||
}
|
||||
@@ -109,8 +113,9 @@ function createAction(prop, targetIds?) {
|
||||
const action: Action = {
|
||||
creatureId: prop.ancestors[0].id,
|
||||
rootPropId: prop._id,
|
||||
taskQueue: [{ propId: prop._id, targetIds }],
|
||||
results: [],
|
||||
taskCount: 0,
|
||||
targetIds,
|
||||
};
|
||||
return Actions.insertAsync(action);
|
||||
}
|
||||
@@ -118,9 +123,9 @@ function createAction(prop, targetIds?) {
|
||||
async function runActionById(propId) {
|
||||
const prop = await CreatureProperties.findOneAsync(propId);
|
||||
const actionId = await createAction(prop);
|
||||
await runActionWork(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;
|
||||
}
|
||||
|
||||
@@ -148,7 +153,7 @@ function allLogContent(action: Action) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
let note1Id, ifTruthyBranchId, ifFalsyBranchId, indexBranchId, choiceBranchId, adjustedStatId,
|
||||
let note1Id, folderId, ifTruthyBranchId, ifFalsyBranchId, indexBranchId, choiceBranchId, adjustedStatId,
|
||||
adjustmentIncrementId, adjustmentSetId, rollId;
|
||||
|
||||
const propForest = [
|
||||
@@ -160,6 +165,12 @@ const propForest = [
|
||||
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
|
||||
{
|
||||
_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 [];
|
||||
// This propertyId will always appear in the parent of the children
|
||||
if (loadedCreatures.has(creatureId)) {
|
||||
const creature = loadedCreatures.get(creatureId);
|
||||
const props = [];
|
||||
for (const prop of creature.properties.values()) {
|
||||
if (prop.parent?.id === propertyId) {
|
||||
if (prop.parent?.id === property._id) {
|
||||
props.push(prop);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +212,7 @@ export function getPropertyChildren(creatureId, propertyId) {
|
||||
return cloneProps.sort((a, b) => a.order - b.order);
|
||||
} else {
|
||||
return CreatureProperties.find({
|
||||
'parent.id': propertyId,
|
||||
'parent.id': property._id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
|
||||
Reference in New Issue
Block a user