diff --git a/app/.meteor/versions b/app/.meteor/versions index 4332f7f0..3405fc33 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -45,7 +45,7 @@ geojson-utils@1.0.11 google-oauth@1.4.4 hot-code-push@1.0.4 html-tools@1.1.4 -htmljs@1.2.0 +htmljs@1.2.1 http@2.0.0 id-map@1.1.1 inter-process-messaging@0.1.1 @@ -72,7 +72,7 @@ mobile-status-bar@1.1.0 modern-browsers@0.1.10 modules@0.20.0 modules-runtime@0.13.1 -mongo@1.16.8 +mongo@1.16.9 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 @@ -80,7 +80,7 @@ npm-mongo@4.17.2 oauth@2.2.1 oauth2@1.3.2 ordered-dict@1.1.0 -ostrio:cookies@2.8.0 +ostrio:cookies@2.8.1 ostrio:files@2.3.3 patreon-oauth@0.1.0 peerlibrary:assert@0.3.0 @@ -112,7 +112,7 @@ shell-server@0.5.0 simple:json-routes@2.3.1 simple:rest@1.2.1 simple:rest-bearer-token-parser@1.1.1 -simple:rest-json-error-handler@1.1.1 +simple:rest-json-error-handler@1.1.3 simple:rest-method-mixin@1.1.0 socket-stream-client@0.5.2 spacebars-compiler@1.3.2 @@ -128,4 +128,4 @@ webapp@1.13.8 webapp-hashing@1.1.1 zer0th:meteor-vuetify-loader@0.1.41 zodern:fix-async-stubs@1.0.2 -zodern:types@1.0.11 +zodern:types@1.0.13 diff --git a/app/client/game-icons.css b/app/client/game-icons.css index 8a28a3c4..d694ff66 100644 --- a/app/client/game-icons.css +++ b/app/client/game-icons.css @@ -5,8 +5,8 @@ @font-face { font-family: "game-icons"; src: url("/fonts/game-icons.eot?817af6e52c83163eb30ece54d9f7d16d?#iefix") format("embedded-opentype"), -url("/fonts/game-icons.woff?817af6e52c83163eb30ece54d9f7d16d") format("woff"), -url("/fonts/game-icons.ttf?817af6e52c83163eb30ece54d9f7d16d") format("truetype"); + url("/fonts/game-icons.woff?817af6e52c83163eb30ece54d9f7d16d") format("woff"), + url("/fonts/game-icons.ttf?817af6e52c83163eb30ece54d9f7d16d") format("truetype"); } .game-icon { diff --git a/app/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js b/app/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js index 497943b4..ebae17f4 100644 --- a/app/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js +++ b/app/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js @@ -22,7 +22,7 @@ const insertCreatureFolder = new ValidatedMethod({ owner: userId }, { fields: { order: 1 }, - sort: { order: -1 } + sort: { left: -1 } }); if (existingFolders.count() >= 50) { throw new Meteor.Error('creatureFolders.methods.insert.denied', diff --git a/app/imports/api/creature/creatureFolders/methods.js/reorderCreatureFolder.js b/app/imports/api/creature/creatureFolders/methods.js/reorderCreatureFolder.js index 3cdc7c5a..f57aaa8a 100644 --- a/app/imports/api/creature/creatureFolders/methods.js/reorderCreatureFolder.js +++ b/app/imports/api/creature/creatureFolders/methods.js/reorderCreatureFolder.js @@ -31,7 +31,7 @@ const reorderCreatureFolder = new ValidatedMethod({ owner: userId }, { fields: { order: 1, }, - sort: { order: -1 } + sort: { left: -1 } }).forEach((folder, index) => { if (folder.order !== index) { CreatureFolders.update(_id, { $set: { order: index } }) diff --git a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js index ad5665c3..7c1b1aa7 100644 --- a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js @@ -55,7 +55,7 @@ const duplicateProperty = new ValidatedMethod({ removed: { $ne: true }, }, { limit: DUPLICATE_CHILDREN_LIMIT + 1, - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); // Alert the user if the limit was hit diff --git a/app/imports/api/creature/creatureProperties/methods/getParentRefByTag.js b/app/imports/api/creature/creatureProperties/methods/getParentRefByTag.js index 1c664692..cda68469 100644 --- a/app/imports/api/creature/creatureProperties/methods/getParentRefByTag.js +++ b/app/imports/api/creature/creatureProperties/methods/getParentRefByTag.js @@ -8,7 +8,7 @@ export default function getParentRefByTag(creatureId, tag) { inactive: { $ne: true }, tags: tag, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); return prop && { id: prop._id, collection: 'creatureProperties' }; } diff --git a/app/imports/api/creature/creatureProperties/methods/index.js b/app/imports/api/creature/creatureProperties/methods/index.js index e66019cc..05aba1ce 100644 --- a/app/imports/api/creature/creatureProperties/methods/index.js +++ b/app/imports/api/creature/creatureProperties/methods/index.js @@ -1,6 +1,5 @@ import '/imports/api/creature/creatureProperties/methods/adjustQuantity'; import '/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary'; -import '/imports/api/creature/creatureProperties/methods/damageProperty'; import '/imports/api/creature/creatureProperties/methods/duplicateProperty'; import '/imports/api/creature/creatureProperties/methods/equipItem'; import '/imports/api/creature/creatureProperties/methods/insertProperty'; diff --git a/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js b/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js index f582ad29..87fc681a 100644 --- a/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js +++ b/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js @@ -159,7 +159,7 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0) { ...getFilter.descendants(referencedNode), removed: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); // We are adding the referenced node and its descendants diff --git a/app/imports/api/creature/creatureProperties/methods/restoreProperty.js b/app/imports/api/creature/creatureProperties/methods/restoreProperty.js index 37713d56..05e0a316 100644 --- a/app/imports/api/creature/creatureProperties/methods/restoreProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/restoreProperty.js @@ -23,13 +23,7 @@ const restoreProperty = new ValidatedMethod({ assertEditPermission(rootCreature, this.userId); // Do work - restore({ - _id, - collection: CreatureProperties, - extraUpdates: { - $set: { dirty: true } - }, - }); + restore(CreatureProperties, property, { $set: { dirty: true } }); } }); diff --git a/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js b/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js index 7c0753bc..3d62655a 100644 --- a/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js @@ -23,7 +23,7 @@ const softRemoveProperty = new ValidatedMethod({ assertEditPermission(rootCreature, this.userId); // Do work - softRemove({ _id, collection: CreatureProperties }); + softRemove(CreatureProperties, property); } }); diff --git a/app/imports/api/creature/creatures/methods/index.js b/app/imports/api/creature/creatures/methods/index.js index 3e8073b6..25715c2c 100644 --- a/app/imports/api/creature/creatures/methods/index.js +++ b/app/imports/api/creature/creatures/methods/index.js @@ -1,5 +1,4 @@ import '/imports/api/creature/creatures/methods/insertCreature'; import '/imports/api/creature/creatures/methods/removeCreature'; -import '/imports/api/creature/creatures/methods/restCreature'; import '/imports/api/creature/creatures/methods/updateCreature'; import '/imports/api/creature/creatures/methods/changeAllowedLibraries'; diff --git a/app/imports/api/creature/creatures/methods/restCreature.js b/app/imports/api/creature/creatures/methods/restCreature.js deleted file mode 100644 index e8768b33..00000000 --- a/app/imports/api/creature/creatures/methods/restCreature.js +++ /dev/null @@ -1,169 +0,0 @@ -import SimpleSchema from 'simpl-schema'; -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/creature/creatures/creaturePermissions'; -import { union } from 'lodash'; -import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty'; -import { getFilter } from '/imports/api/parenting/parentingFunctions'; - -const restCreature = new ValidatedMethod({ - name: 'creature.methods.rest', - validate: new SimpleSchema({ - creatureId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - restType: { - type: String, - allowedValues: ['shortRest', 'longRest'], - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ creatureId, restType }) { - // Get action context - const actionContext = new ActionContext(creatureId, [creatureId], this); - // Check permissions - assertEditPermission(actionContext.creature, this.userId); - - // Join, sort, and apply before triggers - const beforeTriggers = union( - actionContext.triggers.anyRest?.before, actionContext.triggers[restType]?.before - ).sort((a, b) => a.order - b.order); - applyTriggers(beforeTriggers, null, actionContext); - - // Rest - actionContext.addLog({ - name: restType === 'shortRest' ? 'Short rest' : 'Long rest', - }); - doRestWork(restType, actionContext); - - // Join, sort, and apply after triggers - const afterTriggers = union( - actionContext.triggers.anyRest?.after, actionContext.triggers[restType]?.after - ).sort((a, b) => a.order - b.order); - applyTriggers(afterTriggers, null, actionContext); - - // Insert log - actionContext.writeLog(); - }, -}); - -function doRestWork(restType, actionContext) { - const creatureId = actionContext.creature._id; - // Long rests reset short rest properties as well - let resetFilter; - if (restType === 'shortRest') { - resetFilter = 'shortRest' - } else { - resetFilter = { $in: ['shortRest', 'longRest'] } - } - resetProperties(creatureId, resetFilter, actionContext); - - // Reset half hit dice on a long rest, starting with the highest dice - if (restType === 'longRest') { - resetHitDice(creatureId, actionContext); - } -} - -export function resetProperties(creatureId, resetFilter, actionContext) { - // Only apply to active properties - const filter = { - ...getFilter.descendantsOfRoot(creatureId), - reset: resetFilter, - removed: { $ne: true }, - inactive: { $ne: true }, - }; - // update all attribute's damage - const attributeFilter = { - ...filter, - type: 'attribute', - damage: { $nin: [0, undefined] }, - } - CreatureProperties.find(attributeFilter).forEach(prop => { - damagePropertyWork({ - prop, - operation: 'increment', - value: -prop.damage ?? 0, - actionContext, - logFunction(increment) { - actionContext.addLog({ - name: prop.name, - value: increment < 0 ? `Restored ${-increment}` : `Removed ${-increment}` - }); - } - }); - }); - // Update all action-like properties' usesUsed - const actionFilter = { - ...filter, - type: { - $in: ['action', 'spell'] - }, - usesUsed: { $nin: [0, undefined] }, - }; - CreatureProperties.find(actionFilter, { - fields: { name: 1, usesUsed: 1 } - }).forEach(prop => { - actionContext.addLog({ - name: prop.name, - value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses` - }); - }); - CreatureProperties.update(actionFilter, { - $set: { - usesUsed: 0, - dirty: true, - } - }, { - selector: { type: 'action' }, - multi: true, - }); -} - -function resetHitDice(creatureId, actionContext) { - let hitDice = CreatureProperties.find({ - ...getFilter.descendantsOfRoot(creatureId), - type: 'attribute', - attributeType: 'hitDice', - removed: { $ne: true }, - inactive: { $ne: true }, - }).fetch(); - // Use a collator to do sorting in natural order - let collator = new Intl.Collator('en', { - numeric: true, sensitivity: 'base' - }); - // Get the hit dice in decending order of hitDiceSize - let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize) - hitDice.sort(compare); - // Get the total number of hit dice that can be recovered this rest - let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0); - let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5; - let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1); - // recover each hit dice in turn until the recoverable amount is used up - let amountToRecover; - hitDice.forEach(hd => { - if (!recoverableHd) return; - amountToRecover = Math.min(recoverableHd, hd.damage ?? 0); - if (!amountToRecover) return; - recoverableHd -= amountToRecover; - damagePropertyWork({ - prop: hd, - operation: 'increment', - value: -amountToRecover, - actionContext, - logFunction(increment) { - actionContext.addLog({ - name: hd.name, - value: increment < 0 ? `Restored ${-increment} hit dice` : `Removed ${increment} hit dice` - }); - } - }); - }); -} - -export default restCreature; diff --git a/app/imports/api/docs/Docs.js b/app/imports/api/docs/Docs.js index 4cfa992c..291c045b 100644 --- a/app/imports/api/docs/Docs.js +++ b/app/imports/api/docs/Docs.js @@ -103,7 +103,7 @@ const insertDoc = new ValidatedMethod({ doc.parentId = parentId; - const lastOrder = Docs.find({}, { sort: { order: -1 } }).fetch()[0]?.order || 0; + const lastOrder = Docs.find({}, { sort: { left: -1 } }).fetch()[0]?.order || 0; doc.order = lastOrder + 1; doc.urlName = 'new-doc-' + (lastOrder + 1); diff --git a/app/imports/api/engine/action/EngineActions.ts b/app/imports/api/engine/action/EngineActions.ts index 68749b55..46a7e7c0 100644 --- a/app/imports/api/engine/action/EngineActions.ts +++ b/app/imports/api/engine/action/EngineActions.ts @@ -10,7 +10,7 @@ export interface EngineAction { _stepThrough?: boolean; _decisions?: any[], creatureId: string; - rootPropId: string; + rootPropId?: string; targetIds?: string[]; results: TaskResult[]; taskCount: number; diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts index fa3b372e..3f85a36b 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts @@ -13,6 +13,7 @@ import numberToSignedString from '/imports/api/utility/numberToSignedString'; import { getNumberFromScope } from '/imports/api/creature/creatures/CreatureVariables'; 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'; export default async function applyActionProperty( task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider @@ -72,7 +73,12 @@ export default async function applyActionProperty( await applyChildren(action, prop, targetIds, userInput); } if (prop.actionType === 'event' && prop.variableName) { - resetProperties(action, prop, result, userInput); + await applyResetTask({ + subtaskFn: 'reset', + prop, + eventName: prop.variableName, + targetIds: [action.creatureId], + }, action, result, userInput); } // Finish @@ -244,46 +250,3 @@ function applyCrits(value, scope, resultPushScope) { } return { criticalHit, criticalMiss }; } - -async function resetProperties(action: EngineAction, prop: any, result: TaskResult, userInput: InputProvider) { - const attributes = getPropertiesOfType(action.creatureId, 'attribute'); - for (const att of attributes) { - if (att.removed || att.inactive) continue; - if (att.reset !== prop.variableName) continue; - if (!att.damage) continue; - applyTask(action, { - prop: att, - targetIds: [action.creatureId], - subtaskFn: 'damageProp', - params: { - title: getPropertyTitle(att), - operation: 'increment', - value: -att.damage ?? 0, - targetProp: att, - }, - }, userInput) - } - const actions = [ - ...getPropertiesOfType(action.creatureId, 'action'), - ...getPropertiesOfType(action.creatureId, 'spell'), - ] - for (const act of actions) { - if (act.removed || act.inactive) continue; - if (act.reset !== prop.variableName) continue; - if (!act.usesUsed) continue; - result.mutations.push({ - targetIds: [action.creatureId], - updates: [{ - propId: act._id, - set: { usesUsed: 0 }, - type: act.type, - }], - contents: [{ - name: getPropertyTitle(act), - value: act.usesUsed >= 0 ? `Restored ${act.usesUsed} uses` : `Removed ${-act.usesUsed} uses`, - inline: true, - ...prop.silent && { silenced: true }, - }] - }); - } -} \ No newline at end of file diff --git a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts index eb6cccf0..db469c60 100644 --- a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts @@ -42,7 +42,7 @@ export default async function applyDamageProperty( // roll the dice only and store that string recalculateCalculation(prop.amount, action, 'compile', inputProvider); - const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context); + const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context, inputProvider); if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); } @@ -52,7 +52,7 @@ export default async function applyDamageProperty( context.errors = []; // Resolve the roll to a final value - const { result: reduced } = await resolve('reduce', rolled, scope, context); + const { result: reduced } = await resolve('reduce', rolled, scope, context, inputProvider); result.appendParserContextErrors(context, damageTargets); // Store the result @@ -102,11 +102,11 @@ export default async function applyDamageProperty( recalculateCalculation(prop.save.damageFunction, action, 'compile', inputProvider); context.errors = []; const { result: saveDamageRolled } = await resolve( - 'roll', prop.save.damageFunction.valueNode, scope, context + 'roll', prop.save.damageFunction.valueNode, scope, context, inputProvider ); saveRoll = toString(saveDamageRolled); const { result: saveDamageResult } = await resolve( - 'reduce', saveDamageRolled, scope, context + 'reduce', saveDamageRolled, scope, context, inputProvider ); result.appendParserContextErrors(context, damageTargets); // If we didn't end up with a constant of finite amount, give up diff --git a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts index e8c53c96..8f76dddc 100644 --- a/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applySavingThrowProperty.ts @@ -4,7 +4,6 @@ import InputProvider from '/imports/api/engine/action/functions/userInput/InputP import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups'; import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope'; import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation'; -import { applyUnresolvedEffects } from '/imports/api/engine/action/methods/doCheck'; import { PropTask } from '/imports/api/engine/action/tasks/Task'; import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; import { getVariables } from '/imports/api/engine/loadCreatures'; @@ -64,11 +63,8 @@ export default async function applySavingThrowProperty( applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider); } - let rollModifierText = numberToSignedString(save.value, true); - let rollModifier = save.value - const { effectBonus, effectString } = applyUnresolvedEffects(save, scope) - rollModifierText += effectString; - rollModifier += effectBonus; + const rollModifierText = numberToSignedString(save.value, true); + const rollModifier = save.value; let value, resultPrefix; if (save.advantage === 1) { diff --git a/app/imports/api/engine/action/functions/spendResources.ts b/app/imports/api/engine/action/functions/spendResources.ts index 66f6310a..b002d180 100644 --- a/app/imports/api/engine/action/functions/spendResources.ts +++ b/app/imports/api/engine/action/functions/spendResources.ts @@ -5,6 +5,7 @@ import recalculateCalculation from '/imports/api/engine/action/functions/recalcu import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; import applyTask from '/imports/api/engine/action/tasks/applyTask'; import { getSingleProperty } from '/imports/api/engine/loadCreatures'; +import { hasAncestorRelationship } from '/imports/api/parenting/parentingFunctions'; export default async function spendResources( action: EngineAction, prop, targetIds: string[], result: TaskResult, userInput @@ -69,8 +70,11 @@ export default async function spendResources( params: { value: quantity, item, + // If the item is an ancestor or descendant of this prop, skip the item's children to avoid + // an infinite loop + skipChildren: hasAncestorRelationship(item, prop), }, }, userInput); } } -} \ No newline at end of file +} diff --git a/app/imports/api/engine/action/functions/userInput/InputProvider.ts b/app/imports/api/engine/action/functions/userInput/InputProvider.ts index 51244a25..87235fdf 100644 --- a/app/imports/api/engine/action/functions/userInput/InputProvider.ts +++ b/app/imports/api/engine/action/functions/userInput/InputProvider.ts @@ -37,8 +37,8 @@ export type Advantage = 0 | 1 | -1; export type CheckParams = { advantage: Advantage; - skillVariableName: string; - abilityVariableName: string; + skillVariableName?: string; + abilityVariableName?: string; dc: number | null; contest?: true; targetSkillVariableName?: string; diff --git a/app/imports/api/engine/action/functions/writeActionResults.ts b/app/imports/api/engine/action/functions/writeActionResults.ts index 2c862f92..5b3f51b6 100644 --- a/app/imports/api/engine/action/functions/writeActionResults.ts +++ b/app/imports/api/engine/action/functions/writeActionResults.ts @@ -5,12 +5,14 @@ import { union } from 'lodash'; import CreatureLogs from '/imports/api/creature/log/CreatureLogs'; import bulkWrite from '/imports/api/engine/shared/bulkWrite'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties'; +import Creatures from '/imports/api/creature/creatures/Creatures'; export default async function writeActionResults(action: EngineAction) { if (!action._id) throw new Meteor.Error('type-error', 'Action does not have an _id'); - EngineActions.remove(action._id); + const engineActionPromise = EngineActions.removeAsync(action._id); const creaturePropUpdates: any[] = []; const logContents: any[] = []; + // Collect all the updates and log content action.results.forEach(result => { result.mutations.forEach(mutation => { @@ -18,7 +20,7 @@ export default async function writeActionResults(action: EngineAction) { logContents.push(...mutationToLogUpdates(mutation)); }); }); - const allTargetIds = union(...logContents.map(c => c.targetIds)); + const allTargetIds: string[] = union(...logContents.map(c => c.targetIds)); // Write the log const logPromise = CreatureLogs.insertAsync({ @@ -30,5 +32,14 @@ export default async function writeActionResults(action: EngineAction) { // Write the bulk updates const bulkWritePromise = bulkWrite(creaturePropUpdates, CreatureProperties); - return Promise.all([logPromise, bulkWritePromise]); + // Mark the creatures as dirty + const creaturePromise = Creatures.updateAsync({ + _id: { $in: [action.creatureId, ...allTargetIds] }, + }, { + $set: { dirty: true }, + }, { + multi: true, + }); + + return Promise.all([engineActionPromise, logPromise, bulkWritePromise, creaturePromise]); } diff --git a/app/imports/api/engine/action/methods/doCheck.js b/app/imports/api/engine/action/methods/doCheck.js deleted file mode 100644 index efdb352d..00000000 --- a/app/imports/api/engine/action/methods/doCheck.js +++ /dev/null @@ -1,139 +0,0 @@ -import SimpleSchema from 'simpl-schema'; -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/creature/creatures/creaturePermissions'; -import rollDice from '/imports/parser/rollDice'; -import numberToSignedString from '/imports/api/utility/numberToSignedString'; -import { getSingleProperty } from '/imports/api/engine/loadCreatures'; - -// TODO Migrate this to the new action engine - -const doCheck = new ValidatedMethod({ - name: 'creatureProperties.doCheck', - validate: new SimpleSchema({ - propId: SimpleSchema.RegEx.Id, - scope: { - type: Object, - blackbox: true, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 10, - timeInterval: 5000, - }, - run({ propId, scope }) { - console.warn('do check not implemented'); - return; - const prop = CreatureProperties.findOne(propId); - if (!prop) throw new Meteor.Error('not-found', 'The property was not found'); - const creatureId = prop.root.id; - const actionContext = new ActionContext(creatureId, [creatureId], this); - Object.assign(actionContext.scope, scope); - actionContext.scope[`#${prop.type}`] = prop; - - // Check permissions - assertEditPermission(actionContext.creature, this.userId); - - // Do the check - doCheckWork({ prop, actionContext }); - }, -}); - -export default doCheck; - -export function doCheckWork({ prop, actionContext }) { - - applyTriggers(actionContext.triggers.check?.before, prop, actionContext); - rollCheck(prop, actionContext); - applyTriggers(actionContext.triggers.check?.after, prop, actionContext); - - // Insert the log - actionContext.writeLog(); -} - -function rollCheck(prop, actionContext) { - const scope = actionContext.scope; - // get the modifier for the roll - let rollModifier; - let logName = `${prop.name} check`; - if (prop.type === 'skill') { - rollModifier = prop.value; - if (prop.skillType === 'save') { - if (prop.name.match(/save/i)) { - logName = prop.name; - } else { - logName = prop.name ? `${prop.name} save` : 'Saving Throw'; - } - } - } else if (prop.type === 'attribute') { - if (prop.attributeType === 'ability') { - rollModifier = prop.modifier; - } else { - rollModifier = prop.value; - } - } else { - throw (`${prop.type} not supported for checks`); - } - - let rollModifierText = numberToSignedString(rollModifier, true); - - const { effectBonus, effectString } = applyUnresolvedEffects(prop, actionContext) - rollModifierText += effectString; - rollModifier += effectBonus; - - let value, values, resultPrefix; - if (scope['~checkAdvantage']?.value === 1) { - logName += ' (Advantage)'; - const [a, b] = rollDice(2, 20); - if (a >= b) { - value = a; - resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `; - } else { - value = b; - resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `; - } - } else if (scope['~checkAdvantage']?.value === -1) { - logName += ' (Disadvantage)'; - const [a, b] = rollDice(2, 20); - if (a <= b) { - value = a; - resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `; - } else { - value = b; - resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `; - } - } else { - values = rollDice(1, 20); - value = values[0]; - resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = ` - } - const result = (value + rollModifier) || 0; - scope['~checkDiceRoll'] = { value }; - scope['~checkRoll'] = { value: result }; - scope['~checkModifier'] = { value: rollModifier }; - actionContext.addLog({ - name: logName, - value: `${resultPrefix} **${result}**`, - }); -} - -// TODO replace this with recalculating and then rolling/reducing the value node -export function applyUnresolvedEffects(prop, actionContext) { - let effectBonus = 0; - let effectString = ''; - if (!prop.effectIds) { - return { effectBonus, effectString }; - } - prop.effectIds.forEach(id => { - const effect = getSingleProperty(actionContext.creature._id, id); - if (!effect.amount?.parseNode) return; - if (effect.operation !== 'add') return; - recalculateCalculation(effect.amount, actionContext, undefined, 'reduce'); - if (typeof effect.amount?.value !== 'number') return; - effectBonus += effect.amount.value; - effectString += ` ${effect.amount.value < 0 ? '-' : '+'} [${effect.amount.calculation}] ${Math.abs(effect.amount.value)}` - }); - return { effectBonus, effectString }; -} diff --git a/app/imports/api/engine/action/tasks/Task.ts b/app/imports/api/engine/action/tasks/Task.ts index eb920ec5..5239c9c0 100644 --- a/app/imports/api/engine/action/tasks/Task.ts +++ b/app/imports/api/engine/action/tasks/Task.ts @@ -1,6 +1,6 @@ import { CheckParams } from '/imports/api/engine/action/functions/userInput/InputProvider'; -type Task = PropTask | DamagePropTask | ItemAsAmmoTask | CheckTask; +type Task = PropTask | DamagePropTask | ItemAsAmmoTask | CheckTask | ResetTask; export default Task; @@ -31,9 +31,17 @@ export type ItemAsAmmoTask = BaseTask & { params: { value: number; item: any; + skipChildren: boolean; }; } export type CheckTask = BaseTask & CheckParams & { subtaskFn: 'check'; } + +export type ResetTask = BaseTask & { + subtaskFn: 'reset', + eventName: string; + // One and only one target + targetIds: [string]; +} diff --git a/app/imports/api/engine/action/tasks/applyDamagePropTask.ts b/app/imports/api/engine/action/tasks/applyDamagePropTask.ts index 65162120..3408fca9 100644 --- a/app/imports/api/engine/action/tasks/applyDamagePropTask.ts +++ b/app/imports/api/engine/action/tasks/applyDamagePropTask.ts @@ -124,8 +124,8 @@ export default async function applyDamagePropTask( type: targetProp.type, }], contents: [{ - name: 'Attribute damaged', - value: `${numberToSignedString(-value)} ${getPropertyTitle(targetProp)}`, + name: increment >= 0 ? 'Attribute damaged' : 'Attribute restored', + value: `${numberToSignedString(-increment)} ${getPropertyTitle(targetProp)}`, inline: true, ...prop.silent && { silenced: true }, }] diff --git a/app/imports/api/engine/action/tasks/applyItemAsAmmoTask.ts b/app/imports/api/engine/action/tasks/applyItemAsAmmoTask.ts index 6a178f65..092b6321 100644 --- a/app/imports/api/engine/action/tasks/applyItemAsAmmoTask.ts +++ b/app/imports/api/engine/action/tasks/applyItemAsAmmoTask.ts @@ -1,6 +1,6 @@ import { EngineAction } from '/imports/api/engine/action/EngineActions'; import { - applyDefaultAfterPropTasks, applyTriggers + applyDefaultAfterPropTasks, applyAfterTasksSkipChildren, applyTriggers } from '/imports/api/engine/action/functions/applyTaskGroups'; import { getEffectiveActionScope @@ -31,7 +31,7 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action: }; value = scope['~ammoConsumed']?.value || 0; - const itemChildren = await getPropertyChildren(action.creatureId, item); + const itemChildren = task.params.skipChildren ? [] : await getPropertyChildren(action.creatureId, item); // Do the quantity adjustment // Check if property has quantity @@ -53,5 +53,10 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action: }); await applyTriggers(action, item, [action.creatureId], 'ammo.after', userInput); - return applyDefaultAfterPropTasks(action, item, task.targetIds, userInput); + + if (task.params.skipChildren) { + return applyAfterTasksSkipChildren(action, item, task.targetIds, userInput); + } else { + return applyDefaultAfterPropTasks(action, item, task.targetIds, userInput); + } } \ No newline at end of file diff --git a/app/imports/api/engine/action/tasks/applyResetTask.ts b/app/imports/api/engine/action/tasks/applyResetTask.ts new file mode 100644 index 00000000..580a8e36 --- /dev/null +++ b/app/imports/api/engine/action/tasks/applyResetTask.ts @@ -0,0 +1,172 @@ +import { EngineAction } from '/imports/api/engine/action/EngineActions'; +import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider'; +import { ResetTask } from '/imports/api/engine/action/tasks/Task'; +import TaskResult from '/imports/api/engine/action/tasks/TaskResult'; +import applyTask from '/imports/api/engine/action/tasks/applyTask'; +import { getCreature, getPropertiesByFilter, getPropertiesOfType } from '/imports/api/engine/loadCreatures'; +import getPropertyTitle from '/imports/api/utility/getPropertyTitle'; + +export default async function applyResetTask( + task: ResetTask, action: EngineAction, result: TaskResult, userInput: InputProvider +): Promise { + // Event name must be defined + if (!task.eventName) return; + + // This task can only be applied to a single target + if (task.targetIds.length !== 1) { + throw new Meteor.Error('wrong-number-of-targets', `Must reset the properties of a single creature at a time, ${task.targetIds.length} targets were provided`) + } + + // Print a title for the event + let name: string; + switch (task.eventName) { + case 'shortRest': + name = 'Short Rest'; + break; + case 'longRest': + name = 'Long Rest'; + break; + default: + name = task.eventName; + break; + } + result.appendLog({ name }, task.targetIds); + + // Reset the properties by this event name + await resetProperties(task, action, result, userInput); + + // Reset hit dice on a long rest, starting with the highest dice + if (task.eventName === 'longRest') { + await resetHitDice(task, action, result, userInput); + } +} + +export async function resetProperties(task: ResetTask, action: EngineAction, result: TaskResult, userInput: InputProvider) { + const creatureId = task.targetIds[0]; + + // Long rests reset short rest properties as well + let mongoFilter: Mongo.Selector + if (task.eventName === 'longRest') { + mongoFilter = { reset: { $in: ['shortRest', 'longRest'] } } + } else { + mongoFilter = { reset: task.eventName }; + } + + const filterFn = (prop) => { + if (task.eventName === 'longRest') { + if (prop.reset !== 'longRest' && prop.reset !== 'shortRest') return false; + } else { + if (prop.reset !== task.eventName) return false; + } + return true; + } + + // Attributes + + const attributeFilter: Mongo.Selector = { + ...mongoFilter, + type: 'attribute', + damage: { $nin: [0, undefined] }, + } + + const attributeFilterFunction = (att) => { + if (att.type !== 'attribute') return false; + if (!filterFn(att)) return false; + if (att.damage === 0 || att.damage === undefined) return false; + return true; + } + + const attributes = getPropertiesByFilter(creatureId, attributeFilterFunction, attributeFilter); + + for (const prop of attributes) { + await applyTask(action, { + prop: task.prop || prop, + targetIds: [action.creatureId], + subtaskFn: 'damageProp', + params: { + title: getPropertyTitle(prop), + operation: 'increment', + value: -prop.damage ?? 0, + targetProp: prop, + }, + }, userInput); + } + + // Action-like properties + + const actionFilter = { + ...mongoFilter, + type: { + $in: ['action', 'spell'] + }, + usesUsed: { $nin: [0, undefined] }, + }; + + const actionFilterFunction = (prop) => { + if (prop.type !== 'action' && prop.type !== 'spell') return false; + if (!filterFn(prop)) return false; + if (prop.usesUsed === 0 || prop.usesUsed === undefined) return false; + return true; + } + + const actionProps = getPropertiesByFilter(creatureId, actionFilterFunction, actionFilter); + + for (const prop of actionProps) { + result.mutations.push({ + targetIds: [creatureId], + updates: [{ + propId: prop._id, + type: prop.type, + set: { usesUsed: 0 }, + }], + contents: [{ + name: prop.name, + value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses` + }], + }); + } +} + +async function resetHitDice(task: ResetTask, action: EngineAction, result: TaskResult, userInput: InputProvider) { + const creatureId = task.targetIds[0]; + + const hitDice = getPropertiesOfType(creatureId, 'hitDice'); + + // Use a collator to do sorting in natural order + const collator = new Intl.Collator('en', { + numeric: true, sensitivity: 'base' + }); + + // Get the hit dice in decending order of hitDiceSize + const compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize) + hitDice.sort(compare); + + // Get the total number of hit dice that can be recovered this rest + const totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0); + const creature = getCreature(creatureId); + const resetMultiplier = creature.settings.hitDiceResetMultiplier || 0.5; + let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1); + + // recover each hit dice in turn until the recoverable amount is used up + let amountToRecover; + for (const hd of hitDice) { + if (!recoverableHd) return; + amountToRecover = Math.min(recoverableHd, hd.damage ?? 0); + if (!amountToRecover) return; + recoverableHd -= amountToRecover; + + // Apply the damage prop task + await applyTask(action, { + prop: task.prop || hd, + targetIds: [creatureId], + subtaskFn: 'damageProp', + params: { + title: getPropertyTitle(hd), + operation: 'increment', + value: -amountToRecover, + targetProp: hd, + }, + }, userInput); + + } +} diff --git a/app/imports/api/engine/action/tasks/applyTask.ts b/app/imports/api/engine/action/tasks/applyTask.ts index f8223e12..b3a809ff 100644 --- a/app/imports/api/engine/action/tasks/applyTask.ts +++ b/app/imports/api/engine/action/tasks/applyTask.ts @@ -7,6 +7,7 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures'; import applyProperties from '/imports/api/engine/action/applyProperties'; import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider'; import applyCheckTask from '/imports/api/engine/action/tasks/applyCheckTask'; +import applyResetTask from '/imports/api/engine/action/tasks/applyResetTask'; // DamagePropTask promises a number of actual damage done export default async function applyTask( @@ -45,6 +46,10 @@ export default async function applyTask( return applyItemAsAmmoTask(task, action, result, inputProvider); case 'check': return applyCheckTask(task, action, result, inputProvider); + case 'reset': + return applyResetTask(task, action, result, inputProvider); + default: + throw 'No case defined for the given subtaskFn'; } } else { // Get property diff --git a/app/imports/api/engine/loadCreatures.ts b/app/imports/api/engine/loadCreatures.ts index 4e091553..21339b1a 100644 --- a/app/imports/api/engine/loadCreatures.ts +++ b/app/imports/api/engine/loadCreatures.ts @@ -94,6 +94,36 @@ export function getPropertiesOfType(creatureId, propType) { return props; } +/** + * Get the properties of a creature that matches the filters given + * @param creatureId The id of the creature + * @param filterFn A function that returns true if the given prop matches the filter + * @param mongoFilter A mongo selector that is exactly equal to the above function + */ +export function getPropertiesByFilter(creatureId, filterFn: (any) => boolean, mongoFilter: Mongo.Selector) { + const creature = loadedCreatures.get(creatureId); + if (creature) { + const props: CreatureProperty[] = [] + for (const prop of creature.properties.values()) { + if (filterFn(prop)) { + props.push(prop); + } + } + props.sort((a, b) => a.left - b.left); + return EJSON.clone(props); + } + // console.time(`Cache miss on creature properties: ${creatureId}`) + const props = CreatureProperties.find({ + 'root.id': creatureId, + 'removed': { $ne: true }, + ...mongoFilter + }, { + sort: { left: 1 }, + }).fetch(); + // console.timeEnd(`Cache miss on creature properties: ${creatureId}`); + return props; +} + export function getCreature(creatureId: string) { const loadedCreature = loadedCreatures.get(creatureId); const loadedCreatureDoc = loadedCreature?.creature; @@ -207,7 +237,7 @@ export function getPropertyChildren(creatureId, property) { 'parentId': property._id, removed: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); } } @@ -238,7 +268,7 @@ class LoadedCreature { 'root.id': creatureId, removed: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }).observeChanges({ added(id, fields) { fields._id = id; diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index 56373c55..b31333ca 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -284,7 +284,7 @@ const softRemoveLibraryNode = new ValidatedMethod({ run({ _id }) { let node = LibraryNodes.findOne(_id); assertNodeEditPermission(node, this.userId); - softRemove({ _id, collection: LibraryNodes }); + softRemove(LibraryNodes, node); } }); @@ -303,7 +303,7 @@ const restoreLibraryNode = new ValidatedMethod({ let node = LibraryNodes.findOne(_id); assertNodeEditPermission(node, this.userId); // Do work - restore(LibraryNodes, _id); + restore(LibraryNodes, node); } }); diff --git a/app/imports/api/library/methods/copyLibraryNodeTo.js b/app/imports/api/library/methods/copyLibraryNodeTo.js index 5c703253..a77f8d39 100644 --- a/app/imports/api/library/methods/copyLibraryNodeTo.js +++ b/app/imports/api/library/methods/copyLibraryNodeTo.js @@ -58,7 +58,7 @@ const copyLibraryNodeTo = new ValidatedMethod({ removed: { $ne: true }, }, { limit: DUPLICATE_CHILDREN_LIMIT + 1, - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); if (decendants.length > DUPLICATE_CHILDREN_LIMIT) { diff --git a/app/imports/api/library/methods/duplicateLibraryNode.js b/app/imports/api/library/methods/duplicateLibraryNode.js index 0983f97b..ee8116b9 100644 --- a/app/imports/api/library/methods/duplicateLibraryNode.js +++ b/app/imports/api/library/methods/duplicateLibraryNode.js @@ -47,7 +47,7 @@ const duplicateLibraryNode = new ValidatedMethod({ removed: { $ne: true }, }, { limit: DUPLICATE_CHILDREN_LIMIT + 1, - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); if (nodes.length > DUPLICATE_CHILDREN_LIMIT) { diff --git a/app/imports/api/parenting/parentingFunctions.ts b/app/imports/api/parenting/parentingFunctions.ts index 758aaf13..96398e53 100644 --- a/app/imports/api/parenting/parentingFunctions.ts +++ b/app/imports/api/parenting/parentingFunctions.ts @@ -5,7 +5,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur import { Mongo } from 'meteor/mongo'; export function getCollectionByName(name: string): Mongo.Collection { - const collection = Mongo.Collection.get(name) + const collection: Mongo.Collection = Mongo.Collection.get(name) if (!collection) { throw new Meteor.Error('bad-collection-reference', `Parent references collection ${name}, which does not exist` @@ -93,16 +93,16 @@ type FilteredDoc = { export function filterToForest( collection: Mongo.Collection, rootId: string, - filter?: Mongo.Query, + filter?: Mongo.Selector, { - options = >{}, + options = >{}, includeFilteredDocAncestors = false, includeFilteredDocDescendants = false } = {} ): TreeNode[] { if (!Meteor.isClient) throw 'Only available on the client'; // Setup the filter - let collectionFilter: Mongo.Query = { + let collectionFilter: Mongo.Selector = { 'root.id': rootId, 'removed': { $ne: true }, }; @@ -113,16 +113,17 @@ export function filterToForest( } } // Set up the options - let collectionSort = { + let collectionSort: Mongo.Options['sort'] = { left: 1 }; - if (options && options.sort) { + if (options.sort) { collectionSort = { ...collectionSort, + // @ts-expect-error go home typescript you're drunk ...options.sort, } } - let collectionOptions: Mongo.Options = { + let collectionOptions: Mongo.Options = { sort: collectionSort, } if (options) { @@ -671,8 +672,8 @@ export function setDocToLastOrder(collection: Mongo.Collection, doc: Tr doc.left = Number.MAX_SAFE_INTEGER; } -export async function rebuildNestedSets(collection: Mongo.Collection, rootId: string) { - const docs = await collection.find({ +export function rebuildNestedSets(collection: Mongo.Collection, rootId: string) { + const docs = collection.find({ 'root.id': rootId, removed: { $ne: true } }, { @@ -681,13 +682,13 @@ export async function rebuildNestedSets(collection: Mongo.Collection, r //Reverse sorting so that arrays can be used as stacks with the first item on top left: 1, }, - }).fetchAsync(); + }).fetch(); const operations = calculateNestedSetOperations(docs); return writeBulkOperations(collection, operations); } -export async function rebuildCreatureNestedSets(creatureId) { +export function rebuildCreatureNestedSets(creatureId) { const docs = getProperties(creatureId); const operations = calculateNestedSetOperations(docs); return writeBulkOperations(CreatureProperties as Mongo.Collection, operations); @@ -823,8 +824,9 @@ export function applyNestedSetProperties(docs: T[]): Forest */ -async function writeBulkOperations(collection: Mongo.Collection, operations) { - if (Meteor.isServer && operations.length) { +function writeBulkOperations(collection: Mongo.Collection, operations) { + if (Meteor.isServer) { + if (!operations.length) return Promise.resolve(); return new Promise((resolve, reject) => { collection.rawCollection().bulkWrite( operations, @@ -841,20 +843,19 @@ async function writeBulkOperations(collection: Mongo.Collection, operat } else { // Don't do latency compensation if there are too many operations, it just causes client // lag without much benefit - const promises = operations.map(op => { + operations.forEach(op => { if (op.updateOne) { - return collection.updateAsync( + collection.update( op.updateOne.filter, op.updateOne.update, ); } else if (op.updateMany) { - return collection.updateAsync( + collection.update( op.updateMany.filter, op.updateMany.update, { multi: true }, ) } }); - return Promise.all(promises); } } diff --git a/app/imports/api/parenting/softRemove.ts b/app/imports/api/parenting/softRemove.ts index 10c49523..3783a3b5 100644 --- a/app/imports/api/parenting/softRemove.ts +++ b/app/imports/api/parenting/softRemove.ts @@ -1,19 +1,28 @@ import { getCollectionByName, getFilter } from '/imports/api/parenting/parentingFunctions'; import { TreeDoc } from '/imports/api/parenting/ChildSchema'; -export async function softRemove(collection: Mongo.Collection | string, doc?: TreeDoc | string) { +export function softRemove(collectionOrName: Mongo.Collection | string, docOrId?: TreeDoc | string) { const removalDate = new Date(); - if (typeof collection === 'string') { - collection = getCollectionByName(collection); + + let collection: Mongo.Collection; + if (typeof collectionOrName === 'string') { + collection = getCollectionByName(collectionOrName); + } else { + collection = collectionOrName; } - if (typeof doc === 'string') { - doc = await collection.findOneAsync(doc); + + let doc: TreeDoc | undefined; + if (typeof docOrId === 'string') { + doc = collection.findOne(docOrId); + } else { + doc = docOrId } if (!doc) { throw new Meteor.Error('not found', 'The document to remove was not found'); } + // Remove this document - const removeDocPromise = collection.updateAsync( + collection.update( doc._id, { $set: { @@ -23,13 +32,11 @@ export async function softRemove(collection: Mongo.Collection | string, $unset: { removedWith: 1, } - }, { - selector: { type: 'any' }, - }, + } ); // Remove all the descendants that have not yet been removed, and set them to be // removed with this document - const removeDescendantsPromise = collection.updateAsync({ + collection.update({ ...getFilter.descendants(doc), removed: { $ne: true }, }, { @@ -39,10 +46,8 @@ export async function softRemove(collection: Mongo.Collection | string, removedWith: doc._id, } }, { - selector: { type: 'any' }, multi: true, }); - return Promise.all([removeDocPromise, removeDescendantsPromise]); } const restoreError = function () { @@ -51,18 +56,26 @@ const restoreError = function () { ); }; -export async function restore(collection: Mongo.Collection | string, doc: TreeDoc | string, extraUpdates?) { - if (typeof collection === 'string') { - collection = getCollectionByName(collection); +export function restore(collectionOrName: Mongo.Collection | string, docOrId: TreeDoc | string, extraUpdates?) { + + let collection: Mongo.Collection; + if (typeof collectionOrName === 'string') { + collection = getCollectionByName(collectionOrName); + } else { + collection = collectionOrName; } - if (typeof doc === 'string') { - const foundDoc = await collection.findOneAsync(doc) - if (!foundDoc) { - throw new Meteor.Error('not found', 'The document to remove was not found'); - } - doc = foundDoc; + + let doc: TreeDoc | undefined; + if (typeof docOrId === 'string') { + doc = collection.findOne(docOrId); + } else { + doc = docOrId } - const numUpdated: number = await collection.updateAsync({ + if (!doc) { + throw new Meteor.Error('not found', 'The document to remove was not found'); + } + + const numUpdated: number = collection.update({ _id: doc._id, removedWith: { $exists: false } }, { @@ -71,11 +84,11 @@ export async function restore(collection: Mongo.Collection | string, do removedAt: 1, }, ...extraUpdates - }, { - selector: { type: 'any' }, }); + if (numUpdated === 0) restoreError(); - return collection.updateAsync({ + + return collection.update({ removedWith: doc._id, }, { $unset: { @@ -84,7 +97,6 @@ export async function restore(collection: Mongo.Collection | string, do removedWith: 1, } }, { - selector: { type: 'any' }, multi: true, - }); + }) + 1; } diff --git a/app/imports/client/ui/components/RollPopup.vue b/app/imports/client/ui/components/RollPopup.vue deleted file mode 100644 index 0aaca754..00000000 --- a/app/imports/client/ui/components/RollPopup.vue +++ /dev/null @@ -1,122 +0,0 @@ - - - - - diff --git a/app/imports/client/ui/components/rolls/Check.vue b/app/imports/client/ui/components/rolls/Check.vue deleted file mode 100644 index 78500a7b..00000000 --- a/app/imports/client/ui/components/rolls/Check.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - diff --git a/app/imports/client/ui/creature/RestButton.vue b/app/imports/client/ui/creature/RestButton.vue index 64244a43..a977899f 100644 --- a/app/imports/client/ui/creature/RestButton.vue +++ b/app/imports/client/ui/creature/RestButton.vue @@ -3,6 +3,7 @@ :loading="loading" :disabled="context.editPermission === false" outlined + :data-id="`rest-btn-${type}`" style="width: 160px;" @click="rest" > @@ -14,7 +15,7 @@ - - diff --git a/app/imports/client/ui/creature/actions/ActionDialog.vue b/app/imports/client/ui/creature/actions/ActionDialog.vue index a2afc5a2..337bc13f 100644 --- a/app/imports/client/ui/creature/actions/ActionDialog.vue +++ b/app/imports/client/ui/creature/actions/ActionDialog.vue @@ -1,73 +1,101 @@ + + diff --git a/app/imports/client/ui/creature/actions/doAction.ts b/app/imports/client/ui/creature/actions/doAction.ts index a04df490..c07b3a48 100644 --- a/app/imports/client/ui/creature/actions/doAction.ts +++ b/app/imports/client/ui/creature/actions/doAction.ts @@ -5,6 +5,7 @@ import EngineActions, { EngineAction } from '/imports/api/engine/action/EngineAc import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider'; import applyAction from '/imports/api/engine/action/functions/applyAction'; import { runAction } from '/imports/api/engine/action/methods/runAction'; +import getDeterministicDiceRoller from '/imports/api/engine/action/functions/userInput/getDeterministicDiceRoller'; /** * Apply an action on the client that first creates the action on both the client and server, then @@ -44,7 +45,7 @@ export default async function doAction( // Either way, call the action method afterwards try { const finishedAction = await applyAction( - action, errorOnInputRequest, { simulate: true, task } + action, getErrorOnInputRequestProvider(action._id), { simulate: true, task } ); return callActionMethod(finishedAction, task); } catch (e) { @@ -76,10 +77,13 @@ const throwInputRequestedError = () => { throw 'input-requested'; } -const errorOnInputRequest: InputProvider = { - nextStep: throwInputRequestedError, - rollDice: throwInputRequestedError, - choose: throwInputRequestedError, - advantage: throwInputRequestedError, - check: throwInputRequestedError, +function getErrorOnInputRequestProvider(actionId) { + const errorOnInputRequest: InputProvider = { + nextStep: throwInputRequestedError, + rollDice: getDeterministicDiceRoller(actionId), + choose: throwInputRequestedError, + advantage: throwInputRequestedError, + check: throwInputRequestedError, + } + return errorOnInputRequest; } diff --git a/app/imports/client/ui/creature/actions/input/AdvantageInput.vue b/app/imports/client/ui/creature/actions/input/AdvantageInput.vue index 1d46ad7e..b1bd1dd4 100644 --- a/app/imports/client/ui/creature/actions/input/AdvantageInput.vue +++ b/app/imports/client/ui/creature/actions/input/AdvantageInput.vue @@ -52,7 +52,6 @@ export default { methods: { emitInput(e) { e = e || 0; - console.log(e); this.$emit('input', e) } } diff --git a/app/imports/client/ui/creature/actions/input/ChoiceInput.vue b/app/imports/client/ui/creature/actions/input/ChoiceInput.vue new file mode 100644 index 00000000..c594cfd1 --- /dev/null +++ b/app/imports/client/ui/creature/actions/input/ChoiceInput.vue @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/app/imports/client/ui/creature/archive/ArchiveDialog.vue b/app/imports/client/ui/creature/archive/ArchiveDialog.vue index d834c604..8113ac42 100644 --- a/app/imports/client/ui/creature/archive/ArchiveDialog.vue +++ b/app/imports/client/ui/creature/archive/ArchiveDialog.vue @@ -155,7 +155,7 @@ export default { const userId = Meteor.userId(); let folders = CreatureFolders.find( {owner: userId, archived: {$ne: true}}, - {sort: {order: 1}}, + {sort: {left: 1}}, ).map(folder => { folder.creatures = Creatures.find( { @@ -189,7 +189,7 @@ export default { const userId = Meteor.userId(); let folders = CreatureFolders.find( {owner: userId}, - {sort: {order: 1}}, + {sort: {left: 1}}, ).map(folder => { folder.creatures = ArchiveCreatureFiles.find( { diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/BuildTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/BuildTab.vue index b4e1bf74..68cdb813 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/BuildTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/BuildTab.vue @@ -319,7 +319,7 @@ export default { removed: {$ne: true}, inactive: {$ne: true}, }, { - sort: {order: 1} + sort: {left: 1} }).fetch(); }, classLevels() { @@ -331,7 +331,7 @@ export default { removed: {$ne: true}, inactive: {$ne: true}, }, { - sort: {order: 1} + sort: {left: 1} }); }, slotBuildTree(){ diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/FeaturesTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/FeaturesTab.vue index 3a127733..9f4f752a 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/FeaturesTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/FeaturesTab.vue @@ -76,7 +76,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }); }, }, diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue index c660367c..13655b90 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue @@ -163,7 +163,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); }, creature() { @@ -188,7 +188,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, carriedItems() { @@ -204,7 +204,7 @@ export default { deactivatedByAncestor: { $ne: true }, deactivatedByToggle: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, equippedItems() { @@ -215,7 +215,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, equipmentParentRef() { diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/JournalTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/JournalTab.vue index bd53a20e..7ea6007c 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/JournalTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/JournalTab.vue @@ -90,7 +90,7 @@ export default { ...getFilter.descendantsOfRoot(this.creatureId), $nor: [getFilter.descendantsOfAll(allNotes)], }, { - sort: {order: 1}, + sort: {left: 1}, }); }, creature(){ diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/SpellsTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/SpellsTab.vue index cfba26e8..a84c9872 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/SpellsTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/SpellsTab.vue @@ -115,7 +115,7 @@ export default { { hideWhenValueZero: true, value: 0 }, ], }, { - sort: { order: 1 } + sort: { left: 1 } }); }, spellLists() { @@ -168,7 +168,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }); }, }, diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue index e2e819e1..ba3b253d 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -12,7 +12,7 @@ v-for="healthBar in properties.attribute.healthBar" :key="healthBar._id" :model="healthBar" - @change="({ type, value }) => incrementChange(healthBar._id, { type, value: -value })" + @change="({ type, value }) => incrementChange(healthBar._id, { type, value })" @click="clickProperty({_id: healthBar._id})" /> @@ -392,7 +392,6 @@ - - diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue index 47fd6bcf..445c5675 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue @@ -212,7 +212,7 @@ export default { removed: {$ne: true}, inactive: {$ne: true}, }, { - sort: {order: 1} + sort: {left: 1} }).fetch(); }, classLevels() { @@ -224,7 +224,7 @@ export default { removed: {$ne: true}, inactive: {$ne: true}, }, { - sort: {order: 1} + sort: {left: 1} }); }, editPermission() { diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue index 1baafe46..a11ffc0f 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue @@ -111,7 +111,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, creature() { @@ -133,7 +133,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }).map(c => { c.items = CreatureProperties.find({ 'parentId': c._id, @@ -143,7 +143,7 @@ export default { deactivatedByAncestor: { $ne: true }, deactivatedByToggle: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }).fetch(); return c; }); @@ -158,7 +158,7 @@ export default { deactivatedByAncestor: { $ne: true }, deactivatedByToggle: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, equippedItems() { @@ -169,7 +169,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, equipmentParentRef() { diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue index d02c246d..2e1d8e62 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue @@ -70,7 +70,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }).fetch(); }, spellsWithoutList() { @@ -96,7 +96,7 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }).map(sl => { sl.spells = CreatureProperties.find({ ...getFilter.descendants(sl), diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue index f83647f8..e558770a 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue @@ -363,7 +363,7 @@ import { uniqBy } from 'lodash'; import { getFilter } from '/imports/api/parenting/parentingFunctions'; const getProperties = function (creature, filter, options = { - sort: { order: 1 } + sort: { left: 1 } }) { if (!creature) return; if (creature.settings.hideUnusedStats) { @@ -434,7 +434,7 @@ export default { deactivatedByToggle: { $ne: true }, showUI: true, }, { - sort: { order: 1 } + sort: { left: 1 } }); }, healthBars() { diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue index e3c2c69d..7f7bd0c1 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue @@ -175,7 +175,7 @@ export default { 'ancestors.id': this.model._id, 'removed': { $ne: true }, }, { - sort: {order: 1} + sort: {left: 1} }).map(prop => { // Get all the props we don't want to show the decendants of and // where they might appear in the ancestor list diff --git a/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue b/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue index 8b6ef6b8..08b53237 100644 --- a/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue +++ b/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue @@ -72,7 +72,6 @@ diff --git a/app/imports/client/ui/docs/DocsRightDrawer.vue b/app/imports/client/ui/docs/DocsRightDrawer.vue index 01831f79..c63f6bc7 100644 --- a/app/imports/client/ui/docs/DocsRightDrawer.vue +++ b/app/imports/client/ui/docs/DocsRightDrawer.vue @@ -47,7 +47,7 @@ export default { return Session.get('editingDocs'); }, docs() { - const docs = Docs.find({ removed: {$ne: true} }, { sort: {order: 1} }).fetch(); + const docs = Docs.find({ removed: {$ne: true} }, { sort: {left: 1} }).fetch(); return docsToForest(docs); }, }, diff --git a/app/imports/client/ui/layouts/Sidebar.vue b/app/imports/client/ui/layouts/Sidebar.vue index 8d371f7d..7ab264bd 100644 --- a/app/imports/client/ui/layouts/Sidebar.vue +++ b/app/imports/client/ui/layouts/Sidebar.vue @@ -113,7 +113,7 @@ export default { const userId = Meteor.userId(); let folders = CreatureFolders.find( { owner: userId, archived: { $ne: true } }, - { sort: { order: 1 } }, + { sort: { left: 1 } }, ).map(folder => { folder.creatures = Creatures.find( { diff --git a/app/imports/client/ui/library/LibraryEditDialog.vue b/app/imports/client/ui/library/LibraryEditDialog.vue index 1ef7f63a..43a0d08e 100644 --- a/app/imports/client/ui/library/LibraryEditDialog.vue +++ b/app/imports/client/ui/library/LibraryEditDialog.vue @@ -190,7 +190,7 @@ export default { removed: true, removedWith: { $exists: false }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, isOwner() { diff --git a/app/imports/client/ui/pages/CharacterList.vue b/app/imports/client/ui/pages/CharacterList.vue index 9a976b5a..f8b1ef46 100644 --- a/app/imports/client/ui/pages/CharacterList.vue +++ b/app/imports/client/ui/pages/CharacterList.vue @@ -112,7 +112,7 @@ export default { const userId = Meteor.userId(); let folders = CreatureFolders.find( { owner: userId, archived: { $ne: true } }, - { sort: { order: 1 } }, + { sort: { left: 1 } }, ).map(folder => { folder.creatures = Creatures.find( { diff --git a/app/imports/client/ui/pages/DocsPage.vue b/app/imports/client/ui/pages/DocsPage.vue index 51dd9014..10779948 100644 --- a/app/imports/client/ui/pages/DocsPage.vue +++ b/app/imports/client/ui/pages/DocsPage.vue @@ -113,13 +113,13 @@ export default { 'parent': undefined, removed: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }); return Docs.find({ 'parentId': this.doc._id, removed: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }) }, siblingDocs() { @@ -128,7 +128,7 @@ export default { 'parentId': this.doc.parent?.id, removed: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }); }, editing() { diff --git a/app/imports/client/ui/properties/PropertyForm.vue b/app/imports/client/ui/properties/PropertyForm.vue index 7e1664e9..c0643a44 100644 --- a/app/imports/client/ui/properties/PropertyForm.vue +++ b/app/imports/client/ui/properties/PropertyForm.vue @@ -167,10 +167,11 @@ style="width: 100%" class="pa-2 no-hover" > - @@ -225,7 +226,7 @@ import InlineComputationField from '/imports/client/ui/properties/forms/shared/I import FormSection, { FormSections } from '/imports/client/ui/properties/forms/shared/FormSection.vue'; import propertyFormIndex from '/imports/client/ui/properties/forms/shared/propertyFormIndex'; import IconColorMenu from '/imports/client/ui/properties/forms/shared/IconColorMenu.vue'; -import CreaturePropertiesTree from '/imports/client/ui/creature/creatureProperties/CreaturePropertiesTree.vue'; +import DescendantPropertiesTree from '/imports/client/ui/creature/creatureProperties/DescendantPropertiesTree.vue'; import OutlinedInput from '/imports/client/ui/properties/viewers/shared/OutlinedInput.vue'; import { getSuggestedChildren } from '/imports/constants/PROPERTIES'; import PROPERTIES from '/imports/constants/PROPERTIES'; @@ -243,7 +244,7 @@ export default { FormSection, FormSections, IconColorMenu, - CreaturePropertiesTree, + DescendantPropertiesTree, OutlinedInput, ...propertyFormIndex, }, diff --git a/app/imports/client/ui/properties/components/actions/ActionCard.vue b/app/imports/client/ui/properties/components/actions/ActionCard.vue index f2bdf8ea..cdc56e37 100644 --- a/app/imports/client/ui/properties/components/actions/ActionCard.vue +++ b/app/imports/client/ui/properties/components/actions/ActionCard.vue @@ -106,7 +106,6 @@ import ActionConditionView from '/imports/client/ui/properties/components/action import AttributeConsumedView from '/imports/client/ui/properties/components/actions/AttributeConsumedView.vue'; import ItemConsumedView from '/imports/client/ui/properties/components/actions/ItemConsumedView.vue'; import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue'; -import RollPopup from '/imports/client/ui/components/RollPopup.vue'; import MarkdownText from '/imports/client/ui/components/MarkdownText.vue'; import CardHighlight from '/imports/client/ui/components/CardHighlight.vue'; import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue'; @@ -121,7 +120,6 @@ export default { ItemConsumedView, MarkdownText, PropertyIcon, - RollPopup, CardHighlight, TreeNodeList, }, @@ -198,7 +196,7 @@ export default { 'ancestors.id': this.model._id, 'removed': { $ne: true }, }, { - sort: {order: 1} + sort: {left: 1} }).map(prop => { // Get all the props we don't want to show the decendants of and // where they might appear in the ancestor list @@ -225,7 +223,12 @@ export default { this.$emit('click', e); }, doAction() { - doAction(this.model, this.$store, this.model._id); + this.doActionLoading = true; + doAction(this.model, this.$store, this.model._id).catch((e) => { + console.error(e); + }).finally(() => { + this.doActionLoading = false; + }); }, } } diff --git a/app/imports/client/ui/properties/components/actions/SelectItemToConsume.vue b/app/imports/client/ui/properties/components/actions/SelectItemToConsume.vue index d1c042b9..1e307984 100644 --- a/app/imports/client/ui/properties/components/actions/SelectItemToConsume.vue +++ b/app/imports/client/ui/properties/components/actions/SelectItemToConsume.vue @@ -46,7 +46,7 @@ export default { removed: {$ne: true}, inactive: {$ne: true}, }, { - sort: {order: 1}, + sort: {left: 1}, fields: {equipped: false}, }); } diff --git a/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue b/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue index ffb0df15..5732e261 100644 --- a/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue +++ b/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue @@ -7,16 +7,14 @@ class="ma-0" style="min-width: 40px;" > -
@@ -40,7 +38,7 @@
-
+ @@ -64,15 +62,11 @@ diff --git a/app/imports/client/ui/properties/components/attributes/HealthBarCardContainer.vue b/app/imports/client/ui/properties/components/attributes/HealthBarCardContainer.vue deleted file mode 100644 index 51dc33e8..00000000 --- a/app/imports/client/ui/properties/components/attributes/HealthBarCardContainer.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - diff --git a/app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue b/app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue index 7b9a8fbe..90dea6f6 100644 --- a/app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue +++ b/app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue @@ -64,9 +64,10 @@ diff --git a/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue b/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue index abfdbcdb..30cfd16d 100644 --- a/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue +++ b/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue @@ -54,7 +54,7 @@ export default { { hideWhenValueZero: true, value: 0 }, ], }, { - sort: { order: 1 }, + sort: { left: 1 }, }).forEach(prop => { if (propComponents[prop.type]) { props.push(prop); diff --git a/app/imports/client/ui/properties/components/folders/folderGroupComponents/SlotBuildTree.vue b/app/imports/client/ui/properties/components/folders/folderGroupComponents/SlotBuildTree.vue index 60f9d455..49e9df68 100644 --- a/app/imports/client/ui/properties/components/folders/folderGroupComponents/SlotBuildTree.vue +++ b/app/imports/client/ui/properties/components/folders/folderGroupComponents/SlotBuildTree.vue @@ -48,14 +48,14 @@ export default { removed: { $ne: true }, inactive: { $ne: true }, }, { - sort: { order: 1 } + sort: { left: 1 } }); const slotIds = slots.map(s => s._id); const slotChildren = CreatureProperties.find({ 'parentId': { $in: slotIds }, removed: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); const tree = nodeArrayToTree([ ...slots.fetch(), diff --git a/app/imports/client/ui/properties/components/inventory/ContainerCard.vue b/app/imports/client/ui/properties/components/inventory/ContainerCard.vue index a9d8ecc9..734cde9b 100644 --- a/app/imports/client/ui/properties/components/inventory/ContainerCard.vue +++ b/app/imports/client/ui/properties/components/inventory/ContainerCard.vue @@ -101,7 +101,7 @@ export default { deactivatedByAncestor: { $ne: true }, deactivatedByToggle: { $ne: true }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }); }, } diff --git a/app/imports/client/ui/properties/components/skills/SkillListTile.vue b/app/imports/client/ui/properties/components/skills/SkillListTile.vue index 827dc1a6..545de2b6 100644 --- a/app/imports/client/ui/properties/components/skills/SkillListTile.vue +++ b/app/imports/client/ui/properties/components/skills/SkillListTile.vue @@ -10,6 +10,7 @@ v-if="!hideModifier" text tile + :loading="checkLoading" :disabled="!context.editPermission" :data-id="`check-btn-${model._id}`" class="pl-3 pr-2 prof-mod mr-1 flex-shrink-0" @@ -58,6 +59,7 @@ import ProficiencyIcon from '/imports/client/ui/properties/shared/ProficiencyIcon.vue'; import numberToSignedString from '/imports/api/utility/numberToSignedString'; import doAction from '/imports/client/ui/creature/actions/doAction'; +import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue'; export default { components: { @@ -101,6 +103,7 @@ export default { this.$emit('click', e); }, check() { + this.checkLoading = true; doAction(this.model, this.$store, `check-btn-${this.model._id}`, { subtaskFn: 'check', prop: this.model, @@ -109,9 +112,12 @@ export default { skillVariableName: this.model.variableName, abilityVariableName: this.model.ability, dc: null, - }).catch(e => { - console.error(e); - }) + }).catch(error => { + snackbar({ text: error.reason || error.message || error.toString() }); + console.error(error); + }).finally(() => { + this.checkLoading = false; + }); }, } } diff --git a/app/imports/client/ui/properties/components/spells/CastSpellWithSlotDialog.vue b/app/imports/client/ui/properties/components/spells/CastSpellWithSlotDialog.vue index 0651be34..8d17e6a0 100644 --- a/app/imports/client/ui/properties/components/spells/CastSpellWithSlotDialog.vue +++ b/app/imports/client/ui/properties/components/spells/CastSpellWithSlotDialog.vue @@ -159,20 +159,7 @@ > Cancel - - Cast - - { - this.damagePropertyLoading = false; - if (error){ - snackbar({text: error.reason}); - console.error(error); + doAction(model, this.$store, model._id, { + subtaskFn: 'damageProp', + prop: model, + targetIds: [model.root.id], + params: { + title: getPropertyTitle(model), + operation: type, + value, + targetProp: model, } + }).catch((error) => { + snackbar({ text: error.reason || error.message || error.toString() }); + console.error(error); + }).finally(() => { + this.damagePropertyLoading = false; }); }, }, diff --git a/app/imports/client/ui/properties/viewers/SkillViewer.vue b/app/imports/client/ui/properties/viewers/SkillViewer.vue index b7385750..a9604589 100644 --- a/app/imports/client/ui/properties/viewers/SkillViewer.vue +++ b/app/imports/client/ui/properties/viewers/SkillViewer.vue @@ -193,7 +193,7 @@ export default { return CreatureProperties.find({ _id: {$in: this.model.proficiencyIds}, }, { - sort: {order: 1} + sort: {left: 1} }).fetch(); }, ability() { diff --git a/app/imports/client/ui/tabletop/TabletopActionCard.vue b/app/imports/client/ui/tabletop/TabletopActionCard.vue index cd84216f..d090d40d 100644 --- a/app/imports/client/ui/tabletop/TabletopActionCard.vue +++ b/app/imports/client/ui/tabletop/TabletopActionCard.vue @@ -10,36 +10,7 @@ class="avatar" :style="{ opacity: active ? '' : '0.5'}" > - - - - { // Get all the props we don't want to show the decendants of and // where they might appear in the ancestor list diff --git a/app/imports/client/ui/tabletop/TabletopComponent.vue b/app/imports/client/ui/tabletop/TabletopComponent.vue index 432d45f9..cb51f8b3 100644 --- a/app/imports/client/ui/tabletop/TabletopComponent.vue +++ b/app/imports/client/ui/tabletop/TabletopComponent.vue @@ -149,7 +149,7 @@ const getProperties = function (creatureId, selector = {}) { ], ...selector, }, { - sort: { order: 1 } + sort: { left: 1 } }); } diff --git a/app/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.vue b/app/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.vue index 0e8cb681..f57db9ee 100644 --- a/app/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.vue +++ b/app/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.vue @@ -327,7 +327,7 @@ export default { const propsById = {}; const props = []; CreatureProperties.find(filter, { - sort: { order: -1 }, + sort: { left: -1 }, fields: { _id: 1, type: 1 }, }).forEach(prop => { props.push(prop); diff --git a/app/imports/server/publications/library.js b/app/imports/server/publications/library.js index 9d69ec3f..2466617c 100644 --- a/app/imports/server/publications/library.js +++ b/app/imports/server/publications/library.js @@ -243,7 +243,7 @@ Meteor.publish('libraryNodes', function (libraryId, extraFields) { LibraryNodes.find({ 'root.id': libraryId, }, { - sort: { order: 1 }, + sort: { left: 1 }, fields, }), ]; @@ -288,7 +288,7 @@ Meteor.publish('softRemovedLibraryNodes', function (libraryId) { removed: true, removedWith: { $exists: false }, }, { - sort: { order: 1 }, + sort: { left: 1 }, }), ]; }); @@ -309,7 +309,7 @@ Meteor.publish('descendantLibraryNodes', function (nodeId) { LibraryNodes.find({ 'ancestors.id': nodeId, }, { - sort: { order: 1 }, + sort: { left: 1 }, }), ]; });