From 1266794db7c3da55ca4610d64702958b7ee59bc1 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Wed, 15 May 2024 16:06:56 +0200 Subject: [PATCH] Fixed subtle trigger bugs that break LoV hit dice extension --- .../applyAdjustmentProperty.ts | 2 +- .../applyProperties/applyDamageProperty.ts | 4 +- .../applyProperties/applyTriggerProperty.ts | 16 ++++++-- .../action/functions/applyTaskGroups.ts | 2 +- .../action/tasks/applyDamagePropTask.ts | 31 +++++++++++++-- .../computeByType/computeTrigger.js | 39 +++++++++++++------ app/imports/api/engine/shared/bulkWrite.ts | 16 +++++--- 7 files changed, 83 insertions(+), 27 deletions(-) diff --git a/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts b/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts index 9b531edd..057147f2 100644 --- a/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts @@ -15,7 +15,7 @@ export default async function applyAdjustmentProperty( const damageTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds; if (damageTargetIds.length > 1) { - return await applyTaskToEachTarget(action, task, damageTargetIds, userInput); + return applyTaskToEachTarget(action, task, damageTargetIds, userInput); } // Get the operation and value and push the damage hooks to the queue diff --git a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts index db469c60..ebd68256 100644 --- a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts @@ -41,7 +41,7 @@ export default async function applyDamageProperty( const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage'; // roll the dice only and store that string - recalculateCalculation(prop.amount, action, 'compile', inputProvider); + await recalculateCalculation(prop.amount, action, 'compile', inputProvider); const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context, inputProvider); if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); @@ -99,7 +99,7 @@ export default async function applyDamageProperty( let damageOnSave, saveProp, saveRoll; if (prop.save) { if (prop.save.damageFunction?.calculation) { - recalculateCalculation(prop.save.damageFunction, action, 'compile', inputProvider); + await recalculateCalculation(prop.save.damageFunction, action, 'compile', inputProvider); context.errors = []; const { result: saveDamageRolled } = await resolve( 'roll', prop.save.damageFunction.valueNode, scope, context, inputProvider diff --git a/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts b/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts index 8cc271d5..3064c444 100644 --- a/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts @@ -1,16 +1,26 @@ import { EngineAction } from '/imports/api/engine/action/EngineActions'; import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; +import recalculateInlineCalculations from '/imports/api/engine/action/functions/recalculateInlineCalculations'; import { PropTask } from '/imports/api/engine/action/tasks/Task'; import getPropertyTitle from '/imports/api/utility/getPropertyTitle'; - export default async function applyTriggerProperty( task: PropTask, action: EngineAction, result, userInput ): Promise { const prop = task.prop; - result.appendLog({ + const logContent = { name: getPropertyTitle(prop), ...prop.silent && { silenced: true }, - }) + } + + // Add the trigger description to the log + if (prop.description?.text) { + await recalculateInlineCalculations(prop.description, action, 'reduce', userInput); + if (prop.description.value) { + logContent.value = prop.description.value; + } + } + + result.appendLog(logContent); return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); } diff --git a/app/imports/api/engine/action/functions/applyTaskGroups.ts b/app/imports/api/engine/action/functions/applyTaskGroups.ts index e291595d..ca0d3d1e 100644 --- a/app/imports/api/engine/action/functions/applyTaskGroups.ts +++ b/app/imports/api/engine/action/functions/applyTaskGroups.ts @@ -131,7 +131,7 @@ export async function applyAfterPropTasksForSomeChildren( export async function applyTriggers( action: EngineAction, prop, targetIds: string[], triggerPath: string, inputProvider: InputProvider ) { - const triggerIds = get(prop?.triggers, triggerPath); + const triggerIds = get(prop?.triggerIds, triggerPath); if (!triggerIds) return; for (const triggerId of triggerIds) { const trigger = await getSingleProperty(action.creatureId, triggerId); diff --git a/app/imports/api/engine/action/tasks/applyDamagePropTask.ts b/app/imports/api/engine/action/tasks/applyDamagePropTask.ts index 3408fca9..15012c7a 100644 --- a/app/imports/api/engine/action/tasks/applyDamagePropTask.ts +++ b/app/imports/api/engine/action/tasks/applyDamagePropTask.ts @@ -10,7 +10,6 @@ import numberToSignedString from '/imports/api/utility/numberToSignedString'; export default async function applyDamagePropTask( task: DamagePropTask, action: EngineAction, result: TaskResult, userInput ): Promise { - const prop = task.prop; if (task.targetIds.length > 1) { @@ -43,7 +42,11 @@ export default async function applyDamagePropTask( } // Run the before triggers which may change scope properties - await applyTriggers(action, targetProp, [action.creatureId], 'damageProperty.before', userInput); + await applyTriggers(action, targetProp, [action.creatureId], 'before', userInput); + + // Create a new result after triggers have run + result = new TaskResult(task.prop._id, task.targetIds); + action.results.push(result); // Refetch the scope properties const scope = await getEffectiveActionScope(action); @@ -105,6 +108,7 @@ export default async function applyDamagePropTask( ...prop.silent && { silenced: true }, }] }); + setScope(result, targetProp, newValue, damage); } else if (operation === 'increment') { const currentValue = targetProp.value || 0; const currentDamage = targetProp.damage || 0; @@ -130,7 +134,28 @@ export default async function applyDamagePropTask( ...prop.silent && { silenced: true }, }] }); + setScope(result, targetProp, newValue, damage); } - await applyTriggers(action, prop, [action.creatureId], 'damageProperty.after', userInput); + await applyTriggers(action, targetProp, [action.creatureId], 'after', userInput); + await applyTriggers(action, targetProp, [action.creatureId], 'afterChildren', userInput); return increment; +} + +// Update the scope with the attribute, but updated to the new value +// TODO ideally we re-write the getEffectiveActionScope code to be more +// getSomethingFromScope which does the same work, but for a single key, and includes all +// updates to the doc returned that are already applied in the result array +function setScope(result, targetProp, newValue, damage) { + // This isn't the defining property, don't bother + if (targetProp.overridden) return; + const key = targetProp.variableName; + // No variable name can't set scope + if (!key) return; + // Make sure scope is defined + if (!result.scope) result.scope = {}; + result.scope[key] = { + ...EJSON.clone(targetProp), + value: newValue, + damage, + }; } \ No newline at end of file diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeTrigger.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeTrigger.js index b1e696ef..268de44a 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeTrigger.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeTrigger.js @@ -8,17 +8,32 @@ export default function computeTrigger(computation, node) { if (prop.inactive) return; // Link triggers to all the properties that would fire them when applied - if (prop.event === 'doActionProperty') { - getEffectTagTargets(prop, computation).forEach(targetId => { - const targetProp = computation.propsById[targetId]; - // Only apply if the trigger matches this property type - if (targetProp.type !== prop.actionPropertyType) return; - let triggerIdArray = get(targetProp, `triggerIds.${prop.timing}`); - if (!triggerIdArray) { - triggerIdArray = []; - set(targetProp, `triggerIds.${prop.timing}`, triggerIdArray); - } - triggerIdArray.push(prop._id); - }); + const tagTargets = getEffectTagTargets(prop, computation); + switch (prop.event) { + case 'doActionProperty': + tagTargets.forEach(targetId => { + const targetProp = computation.propsById[targetId]; + // Only apply if the trigger matches this property type + if (targetProp.type !== prop.actionPropertyType) return; + setTrigger(prop, targetProp); + }); + break; + case 'damageProperty': + tagTargets.forEach(targetId => { + const targetProp = computation.propsById[targetId]; + // Only apply to attributes + if (targetProp.type !== 'attribute') return; + setTrigger(prop, targetProp); + }); + break; } } + +function setTrigger(prop, targetProp) { + let triggerIdArray = get(targetProp, `triggerIds.${prop.timing}`); + if (!triggerIdArray) { + triggerIdArray = []; + set(targetProp, `triggerIds.${prop.timing}`, triggerIdArray); + } + triggerIdArray.push(prop._id); +} \ No newline at end of file diff --git a/app/imports/api/engine/shared/bulkWrite.ts b/app/imports/api/engine/shared/bulkWrite.ts index a3128327..4a9bacd9 100644 --- a/app/imports/api/engine/shared/bulkWrite.ts +++ b/app/imports/api/engine/shared/bulkWrite.ts @@ -19,12 +19,18 @@ export default function bulkWrite(bulkWriteOps, collection): void | Promise // latency compensation and causes flickering function writePropertiesSequentially(bulkWriteOps: any[], collection: Mongo.Collection) { bulkWriteOps.forEach(op => { + const insertOne = op.insertOne; + if (insertOne) { + collection.insert(insertOne); + } const updateOneOrMany = op.updateOne || op.updateMany; - collection.update(updateOneOrMany.filter, updateOneOrMany.update, { - // The bulk code is bypassing validation, so do the same here - // @ts-expect-error Collection 2 has no typescript support - bypassCollection2: true, - }); + if (updateOneOrMany) { + collection.update(updateOneOrMany.filter, updateOneOrMany.update, { + // The bulk code is bypassing validation, so do the same here + // @ts-expect-error Collection 2 has no typescript support + bypassCollection2: true, + }); + } }); }