From c248d8f4a07df06fe9208c9868c6d6abf8d3453f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 24 Feb 2021 13:41:30 +0200 Subject: [PATCH] Weight carried, Net worth, and Attunement implemented and exposed in UI --- app/imports/api/creature/Creatures.js | 28 +++++- app/imports/api/creature/actions/doAction.js | 3 + .../api/creature/actions/spendResources.js | 2 +- .../methods/adjustQuantity.js | 9 +- .../creatureProperties/methods/equipItem.js | 8 +- .../methods/insertProperty.js | 6 ++ .../methods/insertPropertyFromLibraryNode.js | 3 + .../methods/restoreProperty.js | 3 + .../methods/softRemoveProperty.js | 3 + .../methods/updateCreatureProperty.js | 6 ++ .../denormalise/recomputeInventory.js | 93 ++++++++++++------- app/imports/api/parenting/organizeMethods.js | 23 +++-- .../server/publications/singleCharacter.js | 6 +- .../characterSheetTabs/InventoryTab.vue | 60 +++++++++++- .../components/inventory/ItemListTile.vue | 8 +- .../ui/properties/viewers/ItemViewer.vue | 17 ++++ 16 files changed, 224 insertions(+), 54 deletions(-) diff --git a/app/imports/api/creature/Creatures.js b/app/imports/api/creature/Creatures.js index 633e9be9..1d1615a4 100644 --- a/app/imports/api/creature/Creatures.js +++ b/app/imports/api/creature/Creatures.js @@ -92,8 +92,32 @@ let CreatureSchema = new SimpleSchema({ type: SimpleSchema.Integer, defaultValue: 0, }, - // Sum of all weights of items and containers that are carried - 'denormalizedStats.weightCarried': { + // Inventory + 'denormalizedStats.weightTotal': { + type: Number, + defaultValue: 0, + }, + 'denormalizedStats.weightEquipment': { + type: Number, + defaultValue: 0, + }, + 'denormalizedStats.weightCarried': { + type: Number, + defaultValue: 0, + }, + 'denormalizedStats.valueTotal': { + type: Number, + defaultValue: 0, + }, + 'denormalizedStats.valueEquipment': { + type: Number, + defaultValue: 0, + }, + 'denormalizedStats.valueCarried': { + type: Number, + defaultValue: 0, + }, + 'denormalizedStats.itemsAttuned': { type: Number, defaultValue: 0, }, diff --git a/app/imports/api/creature/actions/doAction.js b/app/imports/api/creature/actions/doAction.js index 66dbba0f..229b4c7a 100644 --- a/app/imports/api/creature/actions/doAction.js +++ b/app/imports/api/creature/actions/doAction.js @@ -9,6 +9,7 @@ import { assertEditPermission } from '/imports/api/creature/creaturePermissions. import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; import { nodesToTree } from '/imports/api/parenting/parenting.js'; import applyProperties from '/imports/api/creature/actions/applyProperties.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; const doAction = new ValidatedMethod({ name: 'creatureProperties.doAction', @@ -43,6 +44,8 @@ const doAction = new ValidatedMethod({ }); doActionWork({action, creature, targets, method: this}); + // The acting creature might have used ammo + recomputeInventory(creature._id); // recompute creatures recomputeCreatureByDoc(creature); targets.forEach(target => { diff --git a/app/imports/api/creature/actions/spendResources.js b/app/imports/api/creature/actions/spendResources.js index 5c7c6448..d8c05094 100644 --- a/app/imports/api/creature/actions/spendResources.js +++ b/app/imports/api/creature/actions/spendResources.js @@ -51,7 +51,7 @@ export default function spendResources({prop, log}){ // Now that we have confirmed that there are no errors, do actual work //Items itemQuantityAdjustments.forEach(adjustQuantityWork); - + // Use uses if (prop.usesResult){ CreatureProperties.update(prop._id, { diff --git a/app/imports/api/creature/creatureProperties/methods/adjustQuantity.js b/app/imports/api/creature/creatureProperties/methods/adjustQuantity.js index f80c6799..430e37fe 100644 --- a/app/imports/api/creature/creatureProperties/methods/adjustQuantity.js +++ b/app/imports/api/creature/creatureProperties/methods/adjustQuantity.js @@ -4,7 +4,8 @@ import SimpleSchema from 'simpl-schema'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; -import { recomputePropertyDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; +import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; const adjustQuantity = new ValidatedMethod({ name: 'creatureProperties.adjustQuantity', @@ -30,8 +31,10 @@ const adjustQuantity = new ValidatedMethod({ // Do work adjustQuantityWork({property, operation, value}); - // Changing quantity does not change dependencies, recompute deps - recomputePropertyDependencies(property); + // Changing quantity does not change dependencies, but recomputing the + // inventory changes many deps at once, so recompute fully + recomputeCreatureByDoc(rootCreature); + recomputeInventory(rootCreature._id); }, }); diff --git a/app/imports/api/creature/creatureProperties/methods/equipItem.js b/app/imports/api/creature/creatureProperties/methods/equipItem.js index b14ca3d8..4e043908 100644 --- a/app/imports/api/creature/creatureProperties/methods/equipItem.js +++ b/app/imports/api/creature/creatureProperties/methods/equipItem.js @@ -4,6 +4,8 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; import { organizeDoc } from '/imports/api/parenting/organizeMethods.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; +import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; import INVENTORY_TAGS from '/imports/constants/INVENTORY_TAGS.js'; export function getParentRefByTag(creatureId, tag){ @@ -49,7 +51,7 @@ const equipItem = new ValidatedMethod({ }); let tag = equipped ? INVENTORY_TAGS.equipment : INVENTORY_TAGS.carried; let parentRef = getParentRefByTag(creature._id, tag); - // organizeDoc handles recompuation + organizeDoc.call({ docRef: { id: _id, @@ -57,7 +59,11 @@ const equipItem = new ValidatedMethod({ }, parentRef, order: Number.MAX_SAFE_INTEGER, + skipRecompute: true, }); + + recomputeInventory(creature._id); + recomputeCreatureByDoc(creature); }, }); diff --git a/app/imports/api/creature/creatureProperties/methods/insertProperty.js b/app/imports/api/creature/creatureProperties/methods/insertProperty.js index 5ed2c45a..010bbd2d 100644 --- a/app/imports/api/creature/creatureProperties/methods/insertProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/insertProperty.js @@ -6,6 +6,7 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js import { reorderDocs } from '/imports/api/parenting/order.js'; import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js'; import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; const insertProperty = new ValidatedMethod({ name: 'creatureProperties.insert', @@ -35,6 +36,11 @@ export function insertPropertyWork({property, creature}){ }); // Inserting the active status of the property needs to be denormalised recomputeInactiveProperties(creature._id); + + // Recompute the inventory if it has changed + if (property.type === 'item' || property.type === 'container'){ + recomputeInventory(creature._id); + } // Inserting a creature property invalidates dependencies: full recompute recomputeCreatureByDoc(creature); return _id; diff --git a/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js b/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js index e25f633c..0eb82c7a 100644 --- a/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js +++ b/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js @@ -15,6 +15,7 @@ import { } from '/imports/api/parenting/parenting.js'; import { reorderDocs } from '/imports/api/parenting/order.js'; import { setDocToLastOrder } from '/imports/api/parenting/order.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; const insertPropertyFromLibraryNode = new ValidatedMethod({ name: 'creatureProperties.insertPropertyFromLibraryNode', @@ -97,6 +98,8 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({ // The library properties need to denormalise which of them are inactive recomputeInactiveProperties(rootId); + // Some of the library properties may be items or containers + recomputeInventory(rootCreature._id); // Inserting a creature property invalidates dependencies: full recompute recomputeCreatureByDoc(rootCreature); // Return the docId of the last property, the inserted root property diff --git a/app/imports/api/creature/creatureProperties/methods/restoreProperty.js b/app/imports/api/creature/creatureProperties/methods/restoreProperty.js index 30be2b98..864bdf36 100644 --- a/app/imports/api/creature/creatureProperties/methods/restoreProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/restoreProperty.js @@ -7,6 +7,7 @@ import { restore } from '/imports/api/parenting/softRemove.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js'; import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; const restoreProperty = new ValidatedMethod({ name: 'creatureProperties.restore', @@ -27,6 +28,8 @@ const restoreProperty = new ValidatedMethod({ // Do work restore({_id, collection: CreatureProperties}); + // Items and containers might be restored + recomputeInventory(rootCreature._id); // Parents active status may have changed while it was deleted recomputeInactiveProperties(rootCreature._id); // Changes dependency tree by restoring children diff --git a/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js b/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js index 5a60e8af..328d3d8e 100644 --- a/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/softRemoveProperty.js @@ -6,6 +6,7 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js import { softRemove } from '/imports/api/parenting/softRemove.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; const softRemoveProperty = new ValidatedMethod({ name: 'creatureProperties.softRemove', @@ -26,6 +27,8 @@ const softRemoveProperty = new ValidatedMethod({ // Do work softRemove({_id, collection: CreatureProperties}); + // Potentially changes items and containers + recomputeInventory(rootCreature._id); // Changes dependency tree by removing children recomputeCreatureByDoc(rootCreature); } diff --git a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js index e84b73ce..c831f816 100644 --- a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js @@ -5,6 +5,7 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; const updateCreatureProperty = new ValidatedMethod({ name: 'creatureProperties.update', @@ -52,6 +53,11 @@ const updateCreatureProperty = new ValidatedMethod({ ].includes(path[0])){ recomputeInactiveProperties(rootCreature._id); } + + if (property.type === 'item' || property.type === 'container'){ + // Potentially changes items and containers + recomputeInventory(rootCreature._id); + } // Updating a property is likely to change dependencies, do a full recompute recomputeCreatureByDoc(rootCreature); }, diff --git a/app/imports/api/creature/denormalise/recomputeInventory.js b/app/imports/api/creature/denormalise/recomputeInventory.js index ceb4824e..fbdaf935 100644 --- a/app/imports/api/creature/denormalise/recomputeInventory.js +++ b/app/imports/api/creature/denormalise/recomputeInventory.js @@ -1,5 +1,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; -import nodesToTree from '/imports/api/parenting/parenting.js'; +import Creatures from '/imports/api/creature/Creatures.js'; +import { nodesToTree } from '/imports/api/parenting/parenting.js'; export default function recomputeInventory(creatureId){ let inventoryForest = nodesToTree({ @@ -10,27 +11,27 @@ export default function recomputeInventory(creatureId){ }, deactivatedByAncestor: {$ne: true}, }); - return getChildrenInventoryData(inventoryForest); -} - -function getChildrenInventoryData(forest){ - let data = { - weightTotal: 0, - weightEquipment: 0, - weightCarried: 0, - valueTotal: 0, - valueEquipment: 0, - valueCarried: 0, - } - forest.forEach(tree => { - let treeData = getInventoryData(tree); - for (let key in data){ - data[key] += treeData[key]; - } + let containersToWrite = []; + let data = getChildrenInventoryData(inventoryForest, containersToWrite); + containersToWrite.forEach(container => { + CreatureProperties.update(container._id, {$set: { + contentsWeight: container.contentsWeight, + contentsValue: container.contentsValue, + }}, {selector: {type: 'container'}}); }); + Creatures.update(creatureId, {$set: { + 'denormalizedStats.weightTotal': data.weightTotal, + 'denormalizedStats.weightEquipment': data.weightEquipment, + 'denormalizedStats.weightCarried': data.weightCarried, + 'denormalizedStats.valueTotal': data.valueTotal, + 'denormalizedStats.valueEquipment': data.valueEquipment, + 'denormalizedStats.valueCarried': data.valueCarried, + 'denormalizedStats.itemsAttuned': data.itemsAttuned, + }}); + return data; } -function getInventoryData(tree){ +function getChildrenInventoryData(forest, containersToWrite){ let data = { weightTotal: 0, weightEquipment: 0, @@ -40,24 +41,41 @@ function getInventoryData(tree){ valueCarried: 0, itemsAttuned: 0, } - let childData = getChildrenInventoryData(tree.children); + forest.forEach(tree => { + let treeData = getInventoryData(tree, containersToWrite); + for (let key in data){ + data[key] += treeData[key] || 0; + } + }); + return data; +} + +function getInventoryData(tree, containersToWrite){ + let data = { + weightTotal: 0, + weightEquipment: 0, + weightCarried: 0, + valueTotal: 0, + valueEquipment: 0, + valueCarried: 0, + itemsAttuned: 0, + } + let childData = getChildrenInventoryData(tree.children, containersToWrite); let node = tree.node; if (node.type === 'container'){ - data.weightTotal += node.weight; - data.valueTotal += node.value; - if (node.carried){ - data.weightCarried += node.weight; - data.valueCarried += node.valueCarried; - } - storeContentsData(node, childData); + data.weightTotal += node.weight || 0; + data.valueTotal += node.value || 0; + data.weightCarried += node.weight || 0; + data.valueCarried += node.value || 0; + storeContentsData(node, childData, containersToWrite); } else if (node.type === 'item'){ - data.weightTotal += node.weight * node.quantity; - data.valueTotal += node.value * node.quantity; - data.weightCarried += node.weight * node.quantity; - data.valueCarried += node.valueCarried * node.quantity; + data.weightTotal += (node.weight * node.quantity) || 0; + data.valueTotal += (node.value * node.quantity) || 0; + data.weightCarried += (node.weight * node.quantity) || 0; + data.valueCarried += (node.value * node.quantity) || 0; if (node.equipped){ - data.weightEquipment += node.weight * node.quantity; - data.valueEquipment += node.valueCarried * node.quantity; + data.weightEquipment += (node.weight * node.quantity) || 0; + data.valueEquipment += (node.value * node.quantity) || 0; } if (node.attuned){ data.itemsAttuned += 1; @@ -66,10 +84,14 @@ function getInventoryData(tree){ for (let key in data){ data[key] += childData[key]; } + if (node.carried === false){ + data.weightCarried = 0; + data.valueCarried = 0; + } return data } -function storeContentsData(node, childData){ +function storeContentsData(node, childData, containersToWrite){ let newContentsWeight; if (node.contentsWeightless){ newContentsWeight = 0; @@ -85,4 +107,7 @@ function storeContentsData(node, childData){ node.contentsValue = newContentsValue; node.contentsValueChanged = true; } + if (node.contentsWeightChanged || node.contentsValueChanged){ + containersToWrite.push(node); + } } diff --git a/app/imports/api/parenting/organizeMethods.js b/app/imports/api/parenting/organizeMethods.js index 8b19b718..93263848 100644 --- a/app/imports/api/parenting/organizeMethods.js +++ b/app/imports/api/parenting/organizeMethods.js @@ -20,13 +20,14 @@ const organizeDoc = new ValidatedMethod({ type: Number, // Should end in 0.5 to place it reliably between two existing documents }, + skipRecompute: Boolean, }).validator(), mixins: [RateLimiterMixin], rateLimit: { numRequests: 5, timeInterval: 5000, }, - run({docRef, parentRef, order}) { + run({docRef, parentRef, order, skipRecompute}) { let doc = fetchDocByRef(docRef); let collection = getCollectionByName(docRef.collection); // The user must be able to edit both the doc and its parent to move it @@ -52,15 +53,17 @@ const organizeDoc = new ValidatedMethod({ // Figure out which creatures need to be recalculated after this move let docCreatures = getCreatureAncestors(doc); let parentCreatures = getCreatureAncestors(parent); - let creaturesToRecompute = union(docCreatures, parentCreatures); - // Recompute the creatures - creaturesToRecompute.forEach(id => { - // The active status of some properties might change due to a change in - // ancestry - recomputeInactiveProperties(id); - // Some Dependencies depend on ancestry, so a full recompute is needed - recomputeCreatureById(id); - }); + if (!skipRecompute){ + let creaturesToRecompute = union(docCreatures, parentCreatures); + // Recompute the creatures + creaturesToRecompute.forEach(id => { + // The active status of some properties might change due to a change in + // ancestry + recomputeInactiveProperties(id); + // Some Dependencies depend on ancestry, so a full recompute is needed + recomputeCreatureById(id); + }); + } }, }); diff --git a/app/imports/server/publications/singleCharacter.js b/app/imports/server/publications/singleCharacter.js index 478e15ad..1ad7027b 100644 --- a/app/imports/server/publications/singleCharacter.js +++ b/app/imports/server/publications/singleCharacter.js @@ -3,6 +3,7 @@ import Creatures from '/imports/api/creature/Creatures.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js'; import { assertViewPermission } from '/imports/api/creature/creaturePermissions.js'; +import recomputeInvetory from '/imports/api/creature/denormalise/recomputeInventory.js'; import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js'; import VERSION from '/imports/constants/VERSION.js'; @@ -25,7 +26,10 @@ Meteor.publish('singleCharacter', function(creatureId){ try { assertViewPermission(creature, userId) } catch(e){ return [] } if (creature.computeVersion !== VERSION){ - try { recomputeCreatureById(creatureId) } + try { + recomputeInvetory(creatureId); + recomputeCreatureById(creatureId) + } catch(e){ console.error(e) } } return [ diff --git a/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue b/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue index ca408675..80359410 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue +++ b/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue @@ -1,6 +1,59 @@