From be47b90c322236bd96121418834947f1c34a6e87 Mon Sep 17 00:00:00 2001 From: ThaumRystra Date: Thu, 16 Jan 2025 20:32:30 +0200 Subject: [PATCH] Fixed bug where tag-targeting a calculation's base value would crash the sheet --- .vscode/launch.json | 17 ++++++ .../computeByType/computeCalculation.js | 2 +- .../tstFns/buildTestComputation.ts | 6 +- .../test/tagTargetBaseValue.test.ts | 59 +++++++++++++++++++ app/imports/parser/resolve.ts | 17 +++++- 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 app/imports/api/engine/computation/test/tagTargetBaseValue.test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..766c4223 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Meteor: Test", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}/app", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run-script", + "test" + ], + "outputCapture": "std", + } + ] +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js index 09ba91d4..9e8fd576 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js @@ -119,7 +119,7 @@ export function aggregateCalculationEffects(calcObj, getEffectFromId) { if (aggregator.base) { calcObj.valueNode = call.create({ functionName: 'max', - args: [calcObj.valueNode, aggregator.base] + args: [calcObj.valueNode, ...aggregator.base] }); } // Add diff --git a/app/imports/api/engine/computation/computeComputation/tstFns/buildTestComputation.ts b/app/imports/api/engine/computation/computeComputation/tstFns/buildTestComputation.ts index 9afb9756..bc3f08b3 100644 --- a/app/imports/api/engine/computation/computeComputation/tstFns/buildTestComputation.ts +++ b/app/imports/api/engine/computation/computeComputation/tstFns/buildTestComputation.ts @@ -4,15 +4,15 @@ import { buildComputationFromProps } from '/imports/api/engine/computation/build import propsFromForest from '/imports/api/engine/computation/utility/propsFromForest.testFn'; import { cleanAndValidate } from '/imports/api/utility/TypedSimpleSchema'; -export default function buildTestComputation(testCreature: TestCreature) { +export default function buildTestComputation(testCreature: Partial) { const creature = cleanAndValidate(Creatures.simpleSchema(), { - _id: testCreature._id, + _id: testCreature._id || Random.id(), name: testCreature.name || 'Test Creature', dirty: true, owner: Random.id(), readers: [], writers: [], }); - const props = propsFromForest(testCreature.props, creature._id); + const props = propsFromForest(testCreature.props || [], creature._id); return buildComputationFromProps(props, creature, {}); } diff --git a/app/imports/api/engine/computation/test/tagTargetBaseValue.test.ts b/app/imports/api/engine/computation/test/tagTargetBaseValue.test.ts new file mode 100644 index 00000000..54aa55e1 --- /dev/null +++ b/app/imports/api/engine/computation/test/tagTargetBaseValue.test.ts @@ -0,0 +1,59 @@ +import { expect } from 'chai'; +import buildTestComputation from '/imports/api/engine/computation/computeComputation/tstFns/buildTestComputation' +import computeCreatureComputation from '/imports/api/engine/computation/computeCreatureComputation'; +import { CreaturePropertyTypes } from '/imports/api/creature/creatureProperties/CreatureProperties'; + +describe('Tag targeting', function () { + it('Can target an attribute with a base value', async function () { + const computation = buildTestComputation({ + props: [ + { + _id: 'attributeToTargetId', + type: 'attribute', + attributeType: 'ability', + variableName: 'strength', + baseValue: { calculation: '12' }, + tags: ['tag'], + }, { + _id: 'tagTargetEffectId', + type: 'effect', + operation: 'base', + targetByTags: true, + amount: { + calculation: '20', + }, + targetTags: ['tag'], + } + ] + }); + await computeCreatureComputation(computation); + const prop = computation.propsById['attributeToTargetId'] as CreaturePropertyTypes['attribute']; + expect(prop.value).to.equal(20); + }); + it('Can target a calculation with a base value', async function () { + const computation = buildTestComputation({ + props: [ + { + _id: 'actionToTargetId', + type: 'action', + attackRoll: { + calculation: '12 + 2', + }, + tags: ['tag'], + }, { + _id: 'tagTargetEffectId', + type: 'effect', + operation: 'base', + amount: { + calculation: '20', + }, + targetByTags: true, + targetTags: ['tag'], + } + ] + }); + await computeCreatureComputation(computation); + const prop = computation.propsById['actionToTargetId'] as CreaturePropertyTypes['action']; + expect(prop.attackRoll?.value).to.equal(20); + }); +}); diff --git a/app/imports/parser/resolve.ts b/app/imports/parser/resolve.ts index 2d89d81a..c7ec4cac 100644 --- a/app/imports/parser/resolve.ts +++ b/app/imports/parser/resolve.ts @@ -19,13 +19,13 @@ export default async function resolve( ): Promise { if (!node) throw new Error('Node must be supplied'); const factory = factories[node.parseType]; - const handlerFunction: ResolveLevelFunction = factory[fn]; if (!factory) { throw new Meteor.Error(`Parse node type: ${node.parseType} not implemented`); } + const handlerFunction = getHandlerFunction(fn, factory); if ('resolve' in factory) { return factory.resolve(fn, node as any, scope, context, inputProvider, resolve); - } else if (fn in factory) { + } else if (handlerFunction) { return handlerFunction(node, scope, context, inputProvider, resolve); } else if (fn === 'reduce' && 'roll' in factory) { return factory.roll(node as any, scope, context, inputProvider, resolve) @@ -36,6 +36,19 @@ export default async function resolve( } } +function getHandlerFunction( + fn: ResolveLevel, factory: typeof factories[T['parseType']] +): ResolveLevelFunction | undefined { + if (!(fn in factory)) return; + if (fn === 'roll' && 'roll' in factory) { + return factory.roll as ResolveLevelFunction; + } else if (fn === 'reduce' && 'reduce' in factory) { + return factory.reduce as ResolveLevelFunction; + } else if (fn === 'compile' && 'compile' in factory) { + return factory.compile as ResolveLevelFunction; + } +} + const computationInputProvider: InputProvider = { /** * By default, just roll the dice as usual