From ae5a159e58168b1b96406bdd69fe3d33730d907c Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 2 May 2025 15:38:18 +0200 Subject: [PATCH] Fix TypeScript errors in action engine --- app/imports/@types/validated-method.d.ts | 8 +++--- .../methods/updateCreatureProperty.js | 14 +++------- .../applyActionProperty.test.ts | 24 +++++++++++------ .../applyProperties/applyActionProperty.ts | 9 ++++--- .../applyAdjustmentProperty.ts | 14 +++++++--- .../applyProperties/applyBranchProperty.ts | 26 +++++++++++++++++-- .../applyProperties/applyBuffProperty.test.ts | 8 +++--- .../applyProperties/applyBuffProperty.ts | 7 ++++- .../applyBuffRemoverProperty.test.ts | 7 ++--- .../applyBuffRemoverProperty.ts | 3 ++- .../applyCreatureTemplateProperty.ts | 7 ++++- .../applyDamageProperty.test.ts | 11 ++++---- .../applyProperties/applyDamageProperty.ts | 17 ++++++++---- .../applyFolderProperty.test.ts | 5 ++-- .../applyProperties/applyFolderProperty.ts | 5 ++++ .../applyProperties/applyNoteProperty.ts | 7 ++++- .../applyProperties/applyRollProperty.test.ts | 5 ++-- .../applyProperties/applyRollProperty.ts | 7 ++++- .../applySavingThrowProperty.test.ts | 9 ++++--- .../applySavingThrowProperty.ts | 12 ++++++--- .../applyToggleProperty.test.ts | 5 ++-- .../applyProperties/applyToggleProperty.ts | 14 ++++++++++ .../applyProperties/applyTriggerProperty.ts | 7 ++++- app/imports/api/engine/action/tasks/Task.ts | 2 +- app/imports/api/library/LibraryNodes.ts | 7 +++-- app/imports/api/properties/Adjustments.ts | 4 +-- .../api/properties/CreatureTemplates.ts | 5 ++++ .../api/properties/subSchemas/ErrorSchema.ts | 5 +++- .../properties/subSchemas/computedField.ts | 18 ++++++++----- app/imports/api/sharing/SharingSchema.ts | 12 +++------ app/imports/parser/types/Context.ts | 7 +++-- 31 files changed, 198 insertions(+), 93 deletions(-) diff --git a/app/imports/@types/validated-method.d.ts b/app/imports/@types/validated-method.d.ts index 4c26e353..8cbe19ce 100644 --- a/app/imports/@types/validated-method.d.ts +++ b/app/imports/@types/validated-method.d.ts @@ -13,15 +13,15 @@ declare module 'meteor/mdg:validated-method' { callAsync: Argument extends NoArguments // methods with no argument can be called with () or just a callback ? - & ((unusedArg: any, callback: (error: Meteor.Error, result: Return) => void) => void) - & ((callback: (error: Meteor.Error | undefined, result: Return) => void) => void) + & ((unusedArg: any, callback?: (error: Meteor.Error, result: Return) => void) => void) + & ((callback?: (error: Meteor.Error | undefined, result: Return) => void) => void) & (() => Return) // methods with arguments require those arguments to be called : & (( arg: Argument, - callback: (error: Meteor.Error | undefined, result: Return) => void, + callback?: (error: Meteor.Error | undefined, result: Return) => void, ) => void) & ((arg: Argument) => Return); } -} \ No newline at end of file +} diff --git a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js index 12c90905..61d8a017 100644 --- a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js @@ -1,8 +1,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; -import { assertEditPermission } from '/imports/api/sharing/sharingPermissions'; -import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor'; +import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions'; const updateCreatureProperty = new ValidatedMethod({ name: 'creatureProperties.update', @@ -11,14 +10,10 @@ const updateCreatureProperty = new ValidatedMethod({ // We cannot change these fields with a simple update switch (path[0]) { case 'type': - case 'order': - case 'parent': - case 'ancestors': case 'root': case 'left': case 'right': case 'parentId': - case 'damage': throw new Meteor.Error('Permission denied', 'This property can\'t be updated directly'); } @@ -30,13 +25,12 @@ const updateCreatureProperty = new ValidatedMethod({ }, run({ _id, path, value }) { // Permission - let property = CreatureProperties.findOne(_id, { + const property = CreatureProperties.findOne(_id, { fields: { type: 1, root: 1 } }); - let rootCreature = getRootCreatureAncestor(property); - assertEditPermission(rootCreature, this.userId); + assertDocEditPermission(property, this.userId); - let pathString = path.join('.'); + const pathString = path.join('.'); let modifier; // unset empty values if (value === null || value === undefined) { diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts index 70c25760..8bcc5429 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts @@ -6,7 +6,8 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; import { LogContent, Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult'; import Alea from 'alea'; @@ -19,7 +20,7 @@ const [ attributeResetByEventId, eventActionId, advantageAttackId, advantageEffectId, disadvantageAttackId, disadvantageEffectId, ] = getRandomIds(100); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ // Empty @@ -138,7 +139,8 @@ const actionTestCreature = { _id: consumeResourceId, variableName: 'resourceVar', quantity: { calculation: '2' }, - }] + }], + conditions: [], } }, { @@ -149,7 +151,9 @@ const actionTestCreature = { _id: consumeResourceId, variableName: 'resourceVar', quantity: { calculation: '9001' }, - }] + }], + itemsConsumed: [], + conditions: [], } }, // Events and resetting attributes @@ -172,7 +176,7 @@ const actionTestCreature = { ], } -const actionTargetCreature = { +const actionTargetCreature: TestCreature = { _id: targetCreatureId, props: [ { @@ -184,7 +188,7 @@ const actionTargetCreature = { ] } -const actionTargetCreature2 = { +const actionTargetCreature2: TestCreature = { _id: targetCreature2Id, props: [ { @@ -329,7 +333,9 @@ describe('Apply Action Properties', function () { it('should make attack rolls that roll with advantage', async function () { const prop = await CreatureProperties.findOneAsync(advantageAttackId); - assert.equal(prop.attackRoll.advantage, 1, 'The attack roll should have advantage'); + assert(prop); + assert(prop.type === 'action') + assert.equal(prop.attackRoll?.advantage, 1, 'The attack roll should have advantage'); const action = await runActionById(advantageAttackId, [targetCreatureId]); const expectedMutations: Mutation[] = [ { @@ -349,7 +355,9 @@ describe('Apply Action Properties', function () { it('should make attack rolls that roll with disadvantage', async function () { const prop = await CreatureProperties.findOneAsync(disadvantageAttackId); - assert.equal(prop.attackRoll.disadvantage, 1, 'The attack roll should have disadvantage'); + assert(prop); + assert(prop.type === 'action'); + assert.equal(prop.attackRoll?.disadvantage, 1, 'The attack roll should have disadvantage'); const action = await runActionById(disadvantageAttackId, [targetCreatureId]); const expectedMutations: Mutation[] = [ { diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts index 40fa8c28..dbef6ab3 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts @@ -13,6 +13,7 @@ import { getNumberFromScope } from '/imports/api/creature/creatures/CreatureVari import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider'; import { CalculatedField } from '/imports/api/properties/subSchemas/computedField'; import applyResetTask from '/imports/api/engine/action/tasks/applyResetTask'; +import { CreaturePropertyTypes } from '/imports/api/creature/creatureProperties/CreatureProperties'; export default async function applyActionProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider @@ -101,6 +102,8 @@ async function applyAttackToTarget( task: PropTask, action: EngineAction, attack: CalculatedField, targetId: string, taskResult: TaskResult, userInput: InputProvider ) { + const prop = task.prop as CreaturePropertyTypes['action'] | CreaturePropertyTypes['spell']; + taskResult.pushScope = { '~attackHit': {}, '~attackMiss': {}, @@ -138,7 +141,7 @@ async function applyAttackToTarget( name, value: `${resultPrefix}\n**${result}**`, inline: true, - ...task.prop.silent && { silenced: true }, + ...prop.silent && { silenced: true }, }); if (criticalMiss || result < targetArmor) { @@ -151,12 +154,12 @@ async function applyAttackToTarget( name: 'Error', value: 'Target has no `armor`', inline: true, - ...task.prop.silent && { silenced: true }, + ...prop.silent && { silenced: true }, }, { name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit', value: `${resultPrefix}\n**${result}**`, inline: true, - ...task.prop.silent && { silenced: true }, + ...prop.silent && { silenced: true }, }); } if (contents.length) { diff --git a/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts b/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts index 9f706dbd..841d999a 100644 --- a/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyAdjustmentProperty.ts @@ -7,11 +7,17 @@ import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; import applyTask from '/imports/api/engine/action/tasks/applyTask'; import { getSingleProperty, getVariables } from '/imports/api/engine/loadCreatures'; import getPropertyTitle from '/imports/api/utility/getPropertyTitle'; +import { CreatureProperty } from '/imports/api/creature/creatureProperties/CreatureProperties'; export default async function applyAdjustmentProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider ): Promise { const prop = task.prop; + + if (prop.type !== 'adjustment') { + throw new Meteor.Error('wrong-property', `Expected an adjustment, got ${prop.type} instead`); + } + const damageTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds; if (damageTargetIds.length > 1) { @@ -30,7 +36,7 @@ export default async function applyAdjustmentProperty( // Evaluate the amount await recalculateCalculation(prop.amount, action, 'reduce', userInput); - const value = +prop.amount.value; + const value = Number(prop.amount.value ?? 0); if (!isFinite(value)) { result.appendLog({ name: 'Error', @@ -44,8 +50,8 @@ export default async function applyAdjustmentProperty( throw new Meteor.Error('1 target Expected', 'At this step, only a single target is supported'); } const targetId = damageTargetIds[0]; - let stat; - if (targetId) { + let stat: CreatureProperty | undefined; + if (targetId && prop.stat) { const statId = getVariables(targetId)?.[prop.stat]?._propId; stat = statId && getSingleProperty(targetId, statId); if (!stat?.type) { @@ -64,7 +70,7 @@ export default async function applyAdjustmentProperty( title: getPropertyTitle(prop), operation: prop.operation, value, - targetProp: stat ?? { _id: 'dummyStat', name: prop.stat, type: 'attribute' }, + targetProp: stat ?? { name: prop.stat ?? '' }, }, }, userInput); return applyDefaultAfterPropTasks(action, prop, damageTargetIds, userInput); diff --git a/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts b/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts index 86e7ba68..8c051a2c 100644 --- a/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts @@ -12,10 +12,23 @@ export default async function applyBranchProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider ): Promise { const prop = task.prop; + + if (prop.type !== 'branch') { + throw new Meteor.Error('wrong-property', `Expected a branch, got ${prop.type} instead`); + } + const targets = task.targetIds; switch (prop.branchType) { case 'if': { + if (!prop.condition) { + result.appendLog({ + name: 'Branch Error', + value: 'If branch does not have a condition set', + silenced: prop.silent, + }, targets); + return applyAfterTasksSkipChildren(action, prop, targets, userInput); + } await recalculateCalculation(prop.condition, action, 'reduce', userInput); if (prop.condition?.value) { return applyDefaultAfterPropTasks(action, prop, targets, userInput); @@ -28,8 +41,17 @@ export default async function applyBranchProperty( if (!children.length) { return applyAfterTasksSkipChildren(action, prop, targets, userInput); } + if (!prop.condition) { + result.appendLog({ + name: 'Branch Error', + value: 'Index branch does not have a condition set', + silenced: prop.silent, + }, targets); + return applyAfterTasksSkipChildren(action, prop, targets, userInput); + } await recalculateCalculation(prop.condition, action, 'reduce', userInput); - if (!isFinite(prop.condition?.value)) { + let index = Number(prop.condition.value); + if (!isFinite(index)) { result.appendLog({ name: 'Branch Error', value: `Index did not resolve into a valid number, got \`${prop.condition?.value}\` instead`, @@ -37,7 +59,7 @@ export default async function applyBranchProperty( }, targets); return applyAfterTasksSkipChildren(action, prop, targets, userInput); } - let index = Math.floor(prop.condition?.value); + index = Math.floor(index); if (index < 1) index = 1; if (index > children.length) index = children.length; const child = children[index - 1]; diff --git a/app/imports/api/engine/action/applyProperties/applyBuffProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyBuffProperty.test.ts index f1109e2e..757fd8cc 100644 --- a/app/imports/api/engine/action/applyProperties/applyBuffProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyBuffProperty.test.ts @@ -4,14 +4,15 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; const [ creatureId, targetCreatureId, buffId ] = getRandomIds(100); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { @@ -36,7 +37,7 @@ const actionTestCreature = { ], }; -const actionTargetCreature = { +const actionTargetCreature: TestCreature = { _id: targetCreatureId, props: [ { @@ -87,7 +88,6 @@ describe('Apply Buff Properties', function () { }, left: 1, right: 4, - parentId: null, root: { collection: 'creatures', id: targetCreatureId, diff --git a/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts b/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts index c2e1dad7..c1430379 100644 --- a/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts @@ -24,6 +24,11 @@ export default async function applyBuffProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider ) { const prop = EJSON.clone(task.prop); + + if (prop.type !== 'buff') { + throw new Meteor.Error('wrong-property', `Expected a buff, got ${prop.type} instead`); + } + const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds; // Log the buff and return if there are no targets @@ -55,7 +60,7 @@ export default async function applyBuffProperty( renewDocIds({ docArray: targetPropList, idMap: { - [prop.parentId]: null, + ...prop.parentId && { [prop.parentId]: null }, [prop.root.id]: target, }, collectionMap: { [prop.root.collection]: 'creatures' } diff --git a/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.test.ts index 49d32e75..5a43ff72 100644 --- a/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.test.ts @@ -4,14 +4,15 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; const [ creatureId, otherCreatureId, buffId, removeParentBuffId, removeTargetBuffsId, ] = getRandomIds(100); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { @@ -43,7 +44,7 @@ const actionTestCreature = { ], }; -const actionOtherCreature = { +const actionOtherCreature: TestCreature = { _id: otherCreatureId, props: [ { diff --git a/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.ts b/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.ts index 1b8fd77f..276236fe 100644 --- a/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyBuffRemoverProperty.ts @@ -7,11 +7,12 @@ import getEffectivePropTags from '/imports/api/engine/computation/utility/getEff import { applyDefaultAfterPropTasks, applyTaskToEachTarget } from '/imports/api/engine/action/functions/applyTaskGroups'; import { EngineAction } from '/imports/api/engine/action/EngineActions'; import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider'; +import { CreaturePropertyTypes } from '/imports/api/creature/creatureProperties/CreatureProperties'; export default async function applyBuffRemoverProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider ) { - const prop = task.prop; + const prop = task.prop as CreaturePropertyTypes['buffRemover']; const targetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds; diff --git a/app/imports/api/engine/action/applyProperties/applyCreatureTemplateProperty.ts b/app/imports/api/engine/action/applyProperties/applyCreatureTemplateProperty.ts index 0c25dd85..94e9980b 100644 --- a/app/imports/api/engine/action/applyProperties/applyCreatureTemplateProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyCreatureTemplateProperty.ts @@ -7,7 +7,12 @@ export default async function applyCreatureTemplateProperty( task: PropTask, action: EngineAction, result, userInput ): Promise { const prop = task.prop; - //Log the Creature that is about to be summoned + + if (prop.type !== 'creature') { + throw new Meteor.Error('wrong-property', `Expected a creature, got ${prop.type} instead`); + } + + // Log the Creature that is about to be summoned let logValue = prop.description?.value if (prop.description?.text) { await recalculateInlineCalculations(prop.description, action, 'reduce', userInput); diff --git a/app/imports/api/engine/action/applyProperties/applyDamageProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyDamageProperty.test.ts index 31ab341c..55dab177 100644 --- a/app/imports/api/engine/action/applyProperties/applyDamageProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyDamageProperty.test.ts @@ -4,7 +4,8 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; import { critInputProvider } from '../functions/userInput/inputProviderForTests.testFn'; @@ -12,7 +13,7 @@ const [ creatureId, targetCreatureId, targetCreature2Id, damageTargetId, damageSelfId, targetCreatureHitPointsId, targetCreature2HitPointsId, selfHitPointsId, damageWithEffectsId, effectId, effect2Id, ] = getRandomIds(20); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { @@ -61,7 +62,7 @@ const actionTestCreature = { ], } -const actionTargetCreature = { +const actionTargetCreature: TestCreature = { _id: targetCreatureId, props: [ { @@ -75,7 +76,7 @@ const actionTargetCreature = { ] } -const actionTargetCreature2 = { +const actionTargetCreature2: TestCreature = { _id: targetCreature2Id, props: [ { @@ -247,7 +248,7 @@ describe('Apply Damage Properties', function () { const [ creatureId, damageId, actionId ] = getRandomIds(3); - const testCreature = { + const testCreature: TestCreature = { _id: creatureId, props: [ { diff --git a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts index d1e3aab2..7eb11c15 100644 --- a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts @@ -16,11 +16,17 @@ import InputProvider from '/imports/api/engine/action/functions/userInput/InputP import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags'; import Context from '/imports/parser/types/Context'; import applySavingThrowProperty from '/imports/api/engine/action/applyProperties/applySavingThrowProperty'; +import { assert } from 'chai'; export default async function applyDamageProperty( task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider ) { const prop = task.prop; + + if (prop.type !== 'damage') { + throw new Meteor.Error('wrong-property', `Expected damage, got ${prop.type} instead`); + } + const scope = await getEffectiveActionScope(action); // Choose target @@ -66,7 +72,7 @@ export default async function applyDamageProperty( damage = reduced.value; } } else if (reduced.parseType === 'error') { - prop.amount.value = null; + prop.amount.value = undefined; } else { prop.amount.value = toString(reduced); } @@ -104,6 +110,7 @@ export default async function applyDamageProperty( if (prop.save.damageFunction?.calculation) { await recalculateCalculation(prop.save.damageFunction, action, 'compile', inputProvider); context.errors = []; + assert(prop.save.damageFunction.valueNode, 'Expected value to be defined after recalculateCalculation'); const { result: saveDamageRolled } = await resolve( 'roll', prop.save.damageFunction.valueNode, scope, context, inputProvider ); @@ -154,7 +161,7 @@ export default async function applyDamageProperty( } else { logValue.push( '**Damage on successful save**', - prop.save.damageFunction.calculation, + prop.save.damageFunction?.calculation ?? '', saveRoll ); } @@ -283,14 +290,14 @@ async function dealDamage( healthBars.sort((a, b) => { let diff; if (amount >= 0) { - diff = a.healthBarDamageOrder - b.healthBarDamageOrder; + diff = (a.healthBarDamageOrder ?? 0) - (b.healthBarDamageOrder ?? 0); } else { - diff = a.healthBarHealingOrder - b.healthBarHealingOrder; + diff = (a.healthBarHealingOrder ?? 0) - (b.healthBarHealingOrder ?? 0); } if (Number.isFinite(diff)) { return diff; } else { - return a.order - b.order; + return a.left - b.left; } }); diff --git a/app/imports/api/engine/action/applyProperties/applyFolderProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyFolderProperty.test.ts index 3eec1b2c..bd9e3030 100644 --- a/app/imports/api/engine/action/applyProperties/applyFolderProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyFolderProperty.test.ts @@ -4,14 +4,15 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; const [ creatureId, folderId ] = getRandomIds(100); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { diff --git a/app/imports/api/engine/action/applyProperties/applyFolderProperty.ts b/app/imports/api/engine/action/applyProperties/applyFolderProperty.ts index ca1b3c68..878c785c 100644 --- a/app/imports/api/engine/action/applyProperties/applyFolderProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyFolderProperty.ts @@ -7,5 +7,10 @@ export default async function applyFolderProperty( task: PropTask, action: EngineAction, result, userInput ): Promise { const prop = task.prop; + + if (prop.type !== 'folder') { + throw new Meteor.Error('wrong-property', `Expected a folder, got ${prop.type} instead`); + } + return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); } diff --git a/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts b/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts index 41baa4dd..3a4308d9 100644 --- a/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyNoteProperty.ts @@ -9,7 +9,12 @@ export default async function applyNoteProperty( task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider ): Promise { const prop = task.prop; - const logContent: LogContent & { silenced: boolean } = { + + if (prop.type !== 'note') { + throw new Meteor.Error('wrong-property', `Expected a note, got ${prop.type} instead`); + } + + const logContent: LogContent & { silenced: boolean | undefined; } = { silenced: prop.silent, }; if (prop.name) logContent.name = prop.name; diff --git a/app/imports/api/engine/action/applyProperties/applyRollProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyRollProperty.test.ts index 26faa70d..298500ff 100644 --- a/app/imports/api/engine/action/applyProperties/applyRollProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyRollProperty.test.ts @@ -4,14 +4,15 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; const [ creatureId, rollId, ] = getRandomIds(2); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { diff --git a/app/imports/api/engine/action/applyProperties/applyRollProperty.ts b/app/imports/api/engine/action/applyProperties/applyRollProperty.ts index e7bdaec7..bafc06c6 100644 --- a/app/imports/api/engine/action/applyProperties/applyRollProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyRollProperty.ts @@ -11,6 +11,11 @@ export default async function applyRollProperty( task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider ): Promise { const prop = task.prop; + + if (prop.type !== 'roll') { + throw new Meteor.Error('wrong-property', `Expected a roll, got ${prop.type} instead`); + } + // If there isn't a calculation, just apply the children instead if (!prop.roll?.calculation) { return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); @@ -38,7 +43,7 @@ export default async function applyRollProperty( if (reduced.parseType === 'constant') { prop.roll.value = reduced.value; } else if (reduced.parseType === 'error') { - prop.roll.value = null; + prop.roll.value = undefined; } else { prop.roll.value = toString(reduced); } diff --git a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.test.ts b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.test.ts index ef44505f..52b306bd 100644 --- a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.test.ts @@ -4,14 +4,15 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; const [ creatureId, savingThrowId, targetCreatureId, targetCreature2Id ] = getRandomIds(4); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { @@ -39,7 +40,7 @@ const actionTestCreature = { ], } -const actionTargetCreature = { +const actionTargetCreature: TestCreature = { _id: targetCreatureId, props: [ { @@ -49,7 +50,7 @@ const actionTargetCreature = { }, ], } -const actionTargetCreature2 = { +const actionTargetCreature2: TestCreature = { _id: targetCreature2Id, props: [ { diff --git a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts index e09186a6..73638a7d 100644 --- a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts @@ -16,13 +16,19 @@ export default async function applySavingThrowProperty( const prop = task.prop; + if (prop.type !== 'savingThrow') { + throw new Meteor.Error('wrong-property', `Expected a savingThrow, got ${prop.type} instead`); + } + const saveTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds; if (saveTargetIds.length > 1) { return applyTaskToEachTarget(action, task, saveTargetIds, inputProvider); } - recalculateCalculation(prop.dc, action, 'reduce', inputProvider); + if (prop.dc) { + recalculateCalculation(prop.dc, action, 'reduce', inputProvider); + } if (!isFiniteNode(prop.dc?.valueNode)) { result.appendLog({ @@ -33,7 +39,7 @@ export default async function applySavingThrowProperty( return applyDefaultAfterPropTasks(action, prop, saveTargetIds, inputProvider); } - const dc = (prop.dc?.value); + const dc = Number(prop.dc?.value ?? 0); result.appendLog({ name: getPropertyTitle(prop), value: `DC **${dc}**`, @@ -54,7 +60,7 @@ export default async function applySavingThrowProperty( } // Each target makes the saving throw - const save = getFromScope(prop.stat, getVariables(targetId)); + const save = prop.stat ? getFromScope(prop.stat, getVariables(targetId)) : undefined; if (!save) { result.appendLog({ diff --git a/app/imports/api/engine/action/applyProperties/applyToggleProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyToggleProperty.test.ts index 7d59c303..f4f3b833 100644 --- a/app/imports/api/engine/action/applyProperties/applyToggleProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyToggleProperty.test.ts @@ -4,14 +4,15 @@ import { createTestCreature, getRandomIds, removeAllCreaturesAndProps, - runActionById + runActionById, + TestCreature } from '/imports/api/engine/action/functions/actionEngineTest.testFn'; const [ creatureId, trueToggleId, falseToggleId, ] = getRandomIds(3); -const actionTestCreature = { +const actionTestCreature: TestCreature = { _id: creatureId, props: [ { diff --git a/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts b/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts index f348e8b3..ca76fe7e 100644 --- a/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyToggleProperty.ts @@ -10,6 +10,20 @@ export default async function applyToggle( ): Promise { const prop = task.prop; + + if (prop.type !== 'toggle') { + throw new Meteor.Error('wrong-property', `Expected a toggle, got ${prop.type} instead`); + } + + if (!prop.condition) { + result.appendLog({ + name: 'Toggle Error', + value: 'toggle does not have a condition set', + silenced: prop.silent, + }, task.targetIds); + return applyAfterTasksSkipChildren(action, prop, task.targetIds, inputProvider); + } + await recalculateCalculation(prop.condition, action, 'reduce', inputProvider); if (prop.condition?.value) { return applyDefaultAfterPropTasks(action, prop, task.targetIds, inputProvider); diff --git a/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts b/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts index a7c86268..dec03c10 100644 --- a/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyTriggerProperty.ts @@ -9,7 +9,12 @@ export default async function applyTriggerProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput ): Promise { const prop = task.prop; - const logContent: LogContent & { silenced: boolean } = { + + if (prop.type !== 'trigger') { + throw new Meteor.Error('wrong-property', `Expected a trigger, got ${prop.type} instead`); + } + + const logContent: LogContent & { silenced: boolean | undefined } = { name: getPropertyTitle(prop), silenced: prop.silent, } diff --git a/app/imports/api/engine/action/tasks/Task.ts b/app/imports/api/engine/action/tasks/Task.ts index e3a1072f..43cf4138 100644 --- a/app/imports/api/engine/action/tasks/Task.ts +++ b/app/imports/api/engine/action/tasks/Task.ts @@ -25,7 +25,7 @@ export type DamagePropTask = BaseTask & { title?: string; operation: 'increment' | 'set'; value: number; - targetProp: CreatureProperty; + targetProp: CreatureProperty | { name: string, }; }; } diff --git a/app/imports/api/library/LibraryNodes.ts b/app/imports/api/library/LibraryNodes.ts index 05bf30f3..1df4dd7b 100644 --- a/app/imports/api/library/LibraryNodes.ts +++ b/app/imports/api/library/LibraryNodes.ts @@ -203,11 +203,10 @@ const updateLibraryNode = new ValidatedMethod({ // We cannot change these fields with a simple update switch (path[0]) { case 'type': - case 'order': - case 'parent': - case 'ancestors': - case 'parentId': case 'root': + case 'left': + case 'right': + case 'parentId': return false; } }, diff --git a/app/imports/api/properties/Adjustments.ts b/app/imports/api/properties/Adjustments.ts index 8bc9315d..b0b4a6b5 100644 --- a/app/imports/api/properties/Adjustments.ts +++ b/app/imports/api/properties/Adjustments.ts @@ -18,7 +18,7 @@ const AdjustmentSchema = createPropertySchema({ allowedValues: [ 'self', 'target', - ], + ] as const, }, // The stat this rolls applies to stat: { @@ -28,7 +28,7 @@ const AdjustmentSchema = createPropertySchema({ }, operation: { type: String, - allowedValues: ['set', 'increment'], + allowedValues: ['set', 'increment'] as const, defaultValue: 'increment', }, // Prevent the property from showing up in the log diff --git a/app/imports/api/properties/CreatureTemplates.ts b/app/imports/api/properties/CreatureTemplates.ts index 20d0da72..1560fb55 100644 --- a/app/imports/api/properties/CreatureTemplates.ts +++ b/app/imports/api/properties/CreatureTemplates.ts @@ -24,6 +24,11 @@ const CreatureTemplateSchema = createPropertySchema({ optional: true, max: STORAGE_LIMITS.url, }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); const ComputedOnlyCreatureTemplateSchema = createPropertySchema({ diff --git a/app/imports/api/properties/subSchemas/ErrorSchema.ts b/app/imports/api/properties/subSchemas/ErrorSchema.ts index 9a509918..67cf0de5 100644 --- a/app/imports/api/properties/subSchemas/ErrorSchema.ts +++ b/app/imports/api/properties/subSchemas/ErrorSchema.ts @@ -1,5 +1,6 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; -import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; +import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; +import { Simplify } from 'type-fest'; const ErrorSchema = TypedSimpleSchema.from({ message: { @@ -12,4 +13,6 @@ const ErrorSchema = TypedSimpleSchema.from({ }, }); +export type ErrorSchemaType = Simplify>; + export default ErrorSchema; diff --git a/app/imports/api/properties/subSchemas/computedField.ts b/app/imports/api/properties/subSchemas/computedField.ts index ba409142..92f85bf8 100644 --- a/app/imports/api/properties/subSchemas/computedField.ts +++ b/app/imports/api/properties/subSchemas/computedField.ts @@ -1,5 +1,5 @@ import SimpleSchema from 'simpl-schema'; -import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema'; +import ErrorSchema, { ErrorSchemaType } from '/imports/api/properties/subSchemas/ErrorSchema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; import ParseNode from '/imports/parser/parseTree/ParseNode'; import { ConstantValueType } from '/imports/parser/parseTree/constant'; @@ -15,9 +15,13 @@ export type CalculatedOnlyField = { proficiencyIds?: string[]; unaffected?: ConstantValueType; parseNode?: ParseNode; - parseError?: any; + parseError?: ErrorSchemaType; hash?: number; - errors?: any[]; + advantage?: number; + disadvantage?: number; + fail?: number; + conditional?: string[]; + errors?: ErrorSchemaType[]; } export type CalculatedField = FieldToCalculate & CalculatedOnlyField; @@ -41,19 +45,19 @@ function computedOnlyField(field) { const schemaObj = { // The value (or calculation string) before any effects/proficiencies are applied or rolls made [`${field}.unaffected`]: { - type: SimpleSchema.oneOf(String, Number), + type: SimpleSchema.oneOf(String, Number, Boolean), optional: true, blackbox: true, }, // The value (or calculation string) after applying all effects [`${field}.value`]: { - type: SimpleSchema.oneOf(String, Number), + type: SimpleSchema.oneOf(String, Number, Boolean), optional: true, blackbox: true, }, // The value as a parse node, after applying all effects [`${field}.valueNode`]: { - type: SimpleSchema.oneOf(String, Number), + type: Object, optional: true, blackbox: true, }, @@ -167,7 +171,7 @@ function includeParentFields(field, schemaObj) { // This should rarely be used, since the other two will merge correctly when // uncomputed and computedOnly schemas are merged function computedField(field) { - return computedField(field).extend(computedOnlyField(field)); + return fieldToCompute(field).extend(computedOnlyField(field)); } export { diff --git a/app/imports/api/sharing/SharingSchema.ts b/app/imports/api/sharing/SharingSchema.ts index 6b3a0c8d..1ea3fe43 100644 --- a/app/imports/api/sharing/SharingSchema.ts +++ b/app/imports/api/sharing/SharingSchema.ts @@ -1,14 +1,6 @@ import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS'; -import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; - -export interface Shared { - owner: string, - readers: string[], - writers: string[], - public: boolean, - readersCanCopy?: true, -} +import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema'; const SharingSchema = TypedSimpleSchema.from({ owner: { @@ -47,4 +39,6 @@ const SharingSchema = TypedSimpleSchema.from({ }, }); +export type Shared = InferType; + export default SharingSchema; diff --git a/app/imports/parser/types/Context.ts b/app/imports/parser/types/Context.ts index 59e95416..16cc937f 100644 --- a/app/imports/parser/types/Context.ts +++ b/app/imports/parser/types/Context.ts @@ -1,6 +1,6 @@ export default class Context { - errors: (Error | { type: string; message: string; })[]; + errors: { type: string; message: string }[]; rolls: { number: number; diceSize: number; values: number[]; }[]; options: { [key: string]: any; }; @@ -18,7 +18,10 @@ export default class Context { message: e, }); } else { - this.errors.push(e); + this.errors.push({ + type: 'error', + message: e.message, + }); } }