From f1b001933179ad0d4b1454c6ac2bd8792c181429 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:35:07 +0200 Subject: [PATCH 01/25] Added pull request template --- app/.github/pull_request_template.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/.github/pull_request_template.md diff --git a/app/.github/pull_request_template.md b/app/.github/pull_request_template.md new file mode 100644 index 00000000..78e990e6 --- /dev/null +++ b/app/.github/pull_request_template.md @@ -0,0 +1,16 @@ +# Checklists + +## Adding features + +- [ ] My new pull request has zero code changes +- [ ] I have described the feature I intend to work on +- [ ] I have described how I intend to implement the feature +- [ ] I will wait for comment from the project's maintainers before submitting code changes + +## Fixing bugs +- [ ] I have performed a self-review of my code +- [ ] I have included a link to the relevant github issue or discord post in the description + +# Description + +`Detailed description of your changes` From c7bb4b80977fabe771ed698e8a2d35d593b384e2 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:31:48 +0200 Subject: [PATCH 02/25] Fixed issue where duplicated point buys cause row-id collision in dep graph --- .../buildComputation/linkTypeDependencies.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 85a7b551..a3d65599 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -259,19 +259,23 @@ function linkPointBuy(dependencyGraph, prop) { dependOnCalc({ dependencyGraph, prop, key: 'max' }); dependOnCalc({ dependencyGraph, prop, key: 'cost' }); dependOnCalc({ dependencyGraph, prop, key: 'total' }); - prop.values?.forEach(row => { + prop.values?.forEach((row, index) => { + // Get a unique id for the row because it might be shared among duplicated point buy tables + // prop._id is forced unique by the database, so it can be used instead + const uniqueRowId = prop._id + '_row_' + index; // Wrap the document in a new object so we don't bash it unintentionally const pointBuyRow = { ...row, + _id: uniqueRowId, type: 'pointBuyRow', tableName: prop.name, tableId: prop._id, } - dependencyGraph.addNode(row._id, pointBuyRow); + dependencyGraph.addNode(pointBuyRow._id, pointBuyRow); linkVariableName(dependencyGraph, pointBuyRow); - dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.min' }); - dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.max' }); - dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.cost' }); + dependOnCalc({ dependencyGraph, pointBuyRow, key: 'min' }); + dependOnCalc({ dependencyGraph, pointBuyRow, key: 'max' }); + dependOnCalc({ dependencyGraph, pointBuyRow, key: 'cost' }); }); if (prop.inactive) return; } From d05803946469c1f56d101a00b35d828e3e1a093d Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:46:54 +0200 Subject: [PATCH 03/25] Renewing Ids now renews sub-doc ids as well --- app/imports/api/parenting/parenting.js | 115 +++++++++++++++---------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/app/imports/api/parenting/parenting.js b/app/imports/api/parenting/parenting.js index e5a8cc62..d30efb25 100644 --- a/app/imports/api/parenting/parenting.js +++ b/app/imports/api/parenting/parenting.js @@ -23,31 +23,31 @@ const allowedParenting = { const allParentTypes = new Set(flatten(Object.values(allowedParenting))); -export function canBeParent(type){ +export function canBeParent(type) { return true; //TODO until there is a good reason to disallow certain parenting options, // this should just let the user do whatever return type && allParentTypes.has(type); } -export function getAllowedParents({childType}){ +export function getAllowedParents({ childType }) { return allowedParenting[childType] || generalParents; } -export function isParentAllowed({parentType = 'root', childType}){ +export function isParentAllowed({ parentType = 'root', childType }) { return true; //TODO until there is a good reason to disallow certain parenting options, // this should just let the user do whatever if (!childType) throw 'childType is required'; - let allowedParents = getAllowedParents({childType}); + let allowedParents = getAllowedParents({ childType }); return allowedParents.includes(parentType); } -export function fetchParent({id, collection}){ - return fetchDocByRef({id, collection}); +export function fetchParent({ id, collection }) { + return fetchDocByRef({ id, collection }); } -export function fetchChildren({ collection, parentId, filter = {}, options = {sort: {order: 1}} }){ +export function fetchChildren({ collection, parentId, filter = {}, options = { sort: { order: 1 } } }) { filter['parent.id'] = parentId; let children = []; children.push( @@ -58,37 +58,37 @@ export function fetchChildren({ collection, parentId, filter = {}, options = {so return children; } -export function updateChildren({collection, parentId, filter = {}, modifier, options={}}){ +export function updateChildren({ collection, parentId, filter = {}, modifier, options = {} }) { filter['parent.id'] = parentId; options.multi = true; collection.update(filter, modifier, options); } -export function fetchDescendants({ collection, ancestorId, filter = {}, options}){ +export function fetchDescendants({ collection, ancestorId, filter = {}, options }) { filter['ancestors.id'] = ancestorId; let descendants = []; descendants.push(...collection.find(filter, options).fetch()); return descendants; } -export function updateDescendants({collection, ancestorId, filter = {}, modifier, options={}}){ +export function updateDescendants({ collection, ancestorId, filter = {}, modifier, options = {} }) { filter['ancestors.id'] = ancestorId; options.multi = true; - options.selector = {type: 'any'}; + options.selector = { type: 'any' }; collection.update(filter, modifier, options); } -export function forEachDescendant({collection, ancestorId, filter = {}, options}, callback){ +export function forEachDescendant({ collection, ancestorId, filter = {}, options }, callback) { filter['ancestors.id'] = ancestorId; collection.find(filter, options).forEach(callback); } // 1 database read -export function getAncestry({parentRef, inheritedFields = {}}){ - let parentDoc = fetchDocByRef(parentRef, {fields: inheritedFields}); - let parent = { ...parentRef}; - for (let field in inheritedFields){ - if (inheritedFields[field]){ +export function getAncestry({ parentRef, inheritedFields = {} }) { + let parentDoc = fetchDocByRef(parentRef, { fields: inheritedFields }); + let parent = { ...parentRef }; + for (let field in inheritedFields) { + if (inheritedFields[field]) { parent[field] = parentDoc[field]; } } @@ -97,13 +97,13 @@ export function getAncestry({parentRef, inheritedFields = {}}){ let ancestors = parentDoc.ancestors || []; ancestors.push(parent); - return {parentDoc, parent, ancestors}; + return { parentDoc, parent, ancestors }; } -export function setLineageOfDocs({docArray, oldParent, newAncestry}){ +export function setLineageOfDocs({ docArray, oldParent, newAncestry }) { const newParent = newAncestry[newAncestry.length - 1]; docArray.forEach(doc => { - if(doc.parent.id === oldParent.id){ + if (doc.parent.id === oldParent.id) { doc.parent = newParent; } let oldAncestors = doc.ancestors; @@ -113,26 +113,43 @@ export function setLineageOfDocs({docArray, oldParent, newAncestry}){ }); } +replaceIdFn(sourceMap) + /** * Give documents new random ids and transform their references. * Transform collections of re-IDed docs according to the collection map */ -export function renewDocIds({docArray, collectionMap, idMap = {}}){ +export function renewDocIds({ docArray, collectionMap, idMap = {} }) { // idMap is a map of {oldId: newId} // Get a random generator that's consistent on client and server let randomSrc = DDP.randomStream('renewDocIds'); - // Give new ids and map the changes as {oldId: newId} + // Replaces all the ids of a sub-document array with new random ids + function replaceIds(arr) { + arr?.forEach?.(obj => { + obj._id = randomSrc.id(); + }); + } + docArray.forEach(doc => { + // Give new ids and map the changes as {oldId: newId} let oldId = doc._id; + // Respect the existing map if a document appears in the array twice let newId = idMap[oldId] || randomSrc.id(); doc._id = newId; idMap[oldId] = newId; + + // Replace ids of sub-properties that might have _id fields + replaceIds(doc.resources?.conditions); + replaceIds(doc.resources?.attributesConsumed); + replaceIds(doc.resources?.itemsConsumed); + replaceIds(doc.extraTags); + replaceIds(doc.values); }); // Remap all references using the new IDs const remapReference = ref => { - if (idMap[ref.id]){ + if (idMap[ref.id]) { ref.id = idMap[ref.id]; ref.collection = collectionMap && collectionMap[ref.collection] || ref.collection; } @@ -143,24 +160,26 @@ export function renewDocIds({docArray, collectionMap, idMap = {}}){ }); } -export function updateParent({docRef, parentRef}){ +export function updateParent({ docRef, parentRef }) { let collection = getCollectionByName(docRef.collection); - let oldDoc = fetchDocByRef(docRef, {fields: { - parent: 1, - ancestors: 1, - type: 1, - }}); - let updateOptions = { selector: {type: 'any'} }; + let oldDoc = fetchDocByRef(docRef, { + fields: { + parent: 1, + ancestors: 1, + type: 1, + } + }); + let updateOptions = { selector: { type: 'any' } }; // Skip if we aren't changing the parent id if (oldDoc.parent.id === parentRef.id) return; // Get the parent and its ancestry - let {parentDoc, parent, ancestors} = getAncestry({parentRef}); + let { parentDoc, parent, ancestors } = getAncestry({ parentRef }); // Check that the doc isn't its own ancestor ancestors.forEach(ancestor => { - if (docRef.id === ancestor.id){ + if (docRef.id === ancestor.id) { throw new Meteor.Error('invalid parenting', 'A doc can\'t be its own ancestor') } @@ -168,12 +187,12 @@ export function updateParent({docRef, parentRef}){ // If the doc and its parent are in the same collection, apply the allowed // parent rules based on type - if (docRef.collection === parentRef.collection){ + if (docRef.collection === parentRef.collection) { let parentAllowed = isParentAllowed({ parentType: parentDoc.type, childType: oldDoc.type }); - if (!parentAllowed){ + if (!parentAllowed) { throw new Meteor.Error('invalid parenting', `Can't make ${oldDoc.type} a child of ${parentDoc.type}`) } @@ -181,16 +200,18 @@ export function updateParent({docRef, parentRef}){ // update the document's parenting collection.update(docRef.id, { - $set: {parent, ancestors} + $set: { parent, ancestors } }, updateOptions); // Remove the old ancestors from the descendants updateDescendants({ collection, ancestorId: docRef.id, - modifier: {$pullAll: { - ancestors: oldDoc.ancestors, - }}, + modifier: { + $pullAll: { + ancestors: oldDoc.ancestors, + } + }, options: updateOptions, }); @@ -198,20 +219,22 @@ export function updateParent({docRef, parentRef}){ updateDescendants({ collection, ancestorId: docRef.id, - modifier: {$push: { - ancestors: { - $each: ancestors, - $position: 0, - }, - }}, + modifier: { + $push: { + ancestors: { + $each: ancestors, + $position: 0, + }, + } + }, options: updateOptions, }); } -export function getName(doc){ +export function getName(doc) { if (doc.name) return name; var i = doc.ancestors.length; - while(i--) { + while (i--) { if (doc.ancestors[i].name) return doc.ancestors[i].name; } } From 673f1873730c8492956a9c213129a60372f66840 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:48:30 +0200 Subject: [PATCH 04/25] Removed stray function --- app/imports/api/parenting/parenting.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/imports/api/parenting/parenting.js b/app/imports/api/parenting/parenting.js index d30efb25..23e241ae 100644 --- a/app/imports/api/parenting/parenting.js +++ b/app/imports/api/parenting/parenting.js @@ -113,8 +113,6 @@ export function setLineageOfDocs({ docArray, oldParent, newAncestry }) { }); } -replaceIdFn(sourceMap) - /** * Give documents new random ids and transform their references. * Transform collections of re-IDed docs according to the collection map From c274153c7915eabfe88cb9deaa162026628dee5e Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:12:30 +0200 Subject: [PATCH 05/25] Duplicating properties now renews root sub-doc ids --- .../creature/creatureProperties/methods/duplicateProperty.js | 5 +++-- app/imports/api/library/methods/duplicateLibraryNode.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js index 25044a7d..88e2f900 100644 --- a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js @@ -77,7 +77,8 @@ const duplicateProperty = new ValidatedMethod({ }); // Give the docs new IDs without breaking internal references - renewDocIds({ docArray: nodes }); + const allNodes = [property, ...nodes]; + renewDocIds({ docArray: allNodes }); // Order the root node property.order += 0.5; @@ -86,7 +87,7 @@ const duplicateProperty = new ValidatedMethod({ property.dirty = true; // Insert the properties - CreatureProperties.batchInsert([property, ...nodes]); + CreatureProperties.batchInsert(allNodes); // Tree structure changed by inserts, reorder the tree reorderDocs({ diff --git a/app/imports/api/library/methods/duplicateLibraryNode.js b/app/imports/api/library/methods/duplicateLibraryNode.js index 43aa0a8a..eef9f10c 100644 --- a/app/imports/api/library/methods/duplicateLibraryNode.js +++ b/app/imports/api/library/methods/duplicateLibraryNode.js @@ -67,12 +67,13 @@ const duplicateLibraryNode = new ValidatedMethod({ }); // Give the docs new IDs without breaking internal references - renewDocIds({ docArray: nodes }); + const allNodes = [libraryNode, ...nodes]; + renewDocIds({ docArray: allNodes }); // Order the root node libraryNode.order += 0.5; - LibraryNodes.batchInsert([libraryNode, ...nodes]); + LibraryNodes.batchInsert(allNodes); // Tree structure changed by inserts, reorder the tree reorderDocs({ From 2545b9dd475a1be3232b7cce9e38abfc53a576d9 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:50:35 +0200 Subject: [PATCH 06/25] Added "save for half" to damage form --- app/imports/api/properties/Damages.js | 24 +++- .../client/ui/properties/forms/DamageForm.vue | 111 +++++++++++++++--- .../ui/properties/forms/SavingThrowForm.vue | 1 - 3 files changed, 114 insertions(+), 22 deletions(-) diff --git a/app/imports/api/properties/Damages.js b/app/imports/api/properties/Damages.js index d01a2e0d..b5510444 100644 --- a/app/imports/api/properties/Damages.js +++ b/app/imports/api/properties/Damages.js @@ -33,10 +33,23 @@ const DamageSchema = createPropertySchema({ type: Boolean, optional: true, }, + // remove the entire object if there is no saving throw save: { - type: SavingThrowSchema, + type: Object, optional: true, }, + // The computed DC + 'save.dc': { + type: 'fieldToCompute', + optional: true, + }, + // The variable name of save to roll + 'save.stat': { + type: String, + optional: true, + max: STORAGE_LIMITS.variableName, + }, + // The damage to deal on a successful save 'save.damageFunction': { type: 'fieldToCompute', optional: true, @@ -51,13 +64,18 @@ const ComputedOnlyDamageSchema = createPropertySchema({ parseLevel: 'compile', }, save: { - type: ComputedOnlySavingThrowSchema, + type: Object, + optional: true, + }, + 'save.dc': { + type: 'computedOnlyField', + parseLevel: 'compile', optional: true, }, 'save.damageFunction': { type: 'computedOnlyField', - optional: true, parseLevel: 'compile', + optional: true, }, }); diff --git a/app/imports/client/ui/properties/forms/DamageForm.vue b/app/imports/client/ui/properties/forms/DamageForm.vue index b7ed8881..8c5204c0 100644 --- a/app/imports/client/ui/properties/forms/DamageForm.vue +++ b/app/imports/client/ui/properties/forms/DamageForm.vue @@ -31,25 +31,92 @@ @change="change('damageType', ...arguments)" /> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -60,9 +127,10 @@ import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js'; import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; +import saveListMixin from '/imports/client/ui/properties/forms/shared/lists/saveListMixin.js'; export default { - mixins: [propertyFormMixin], + mixins: [propertyFormMixin, saveListMixin], props: { parentTarget: { type: String, @@ -102,6 +170,13 @@ export default { return hints[this.model.target]; } }, + methods: { + saveChange({ path, value, ack }) { + console.log({ path, value, ack }); + this.$emit('change', {path: [ 'save', ...path ], value, ack}) + this.$emit('change', {path: [ 'silent' ], value: true, ack}) + }, + }, } diff --git a/app/imports/client/ui/properties/forms/SavingThrowForm.vue b/app/imports/client/ui/properties/forms/SavingThrowForm.vue index 4b5aaf25..95050bcc 100644 --- a/app/imports/client/ui/properties/forms/SavingThrowForm.vue +++ b/app/imports/client/ui/properties/forms/SavingThrowForm.vue @@ -29,7 +29,6 @@ Date: Mon, 18 Sep 2023 15:50:48 +0200 Subject: [PATCH 07/25] Relaxed creature property update rate limit --- .../creatureProperties/methods/updateCreatureProperty.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js index 4b606114..5664d8bd 100644 --- a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js @@ -21,7 +21,7 @@ const updateCreatureProperty = new ValidatedMethod({ }, mixins: [RateLimiterMixin], rateLimit: { - numRequests: 5, + numRequests: 12, timeInterval: 5000, }, run({ _id, path, value }) { From 5222c240c7d8523f25a20d6b58cb626720556390 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:50:29 +0200 Subject: [PATCH 08/25] Added saving throw to damage viewer --- .../ui/properties/viewers/DamageViewer.vue | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/imports/client/ui/properties/viewers/DamageViewer.vue b/app/imports/client/ui/properties/viewers/DamageViewer.vue index af8f2829..6d88cf6f 100644 --- a/app/imports/client/ui/properties/viewers/DamageViewer.vue +++ b/app/imports/client/ui/properties/viewers/DamageViewer.vue @@ -16,6 +16,23 @@ name="Target" value="Self" /> + @@ -30,6 +47,16 @@ export default { if (this.model.damageType === 'healing') return this.model.damageType; return `${this.model.damageType} damage` }, + saveDamage() { + if (!this.model.save) return; + if (!this.model.save.damageFunction?.calculation) { + return { value: 'Half damage' }; + } + if (this.model.save.damageFunction.calculation == '0' || this.model.save.damageFunction.value === 0) { + return { value: 'No damage' }; + } + return { calculation: this.model.save.damageFunction }; + } } } From 3be45b28c33efb4ffae09f6dd0e8f26c296fc6a9 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:56:33 +0200 Subject: [PATCH 09/25] Made damage save form a bit more intuitive --- .../client/ui/properties/forms/DamageForm.vue | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/app/imports/client/ui/properties/forms/DamageForm.vue b/app/imports/client/ui/properties/forms/DamageForm.vue index 8c5204c0..067dd22c 100644 --- a/app/imports/client/ui/properties/forms/DamageForm.vue +++ b/app/imports/client/ui/properties/forms/DamageForm.vue @@ -31,9 +31,22 @@ @change="change('damageType', ...arguments)" /> + + + + + + - - - - - - + - - - From f62a8bead4fb90ee67ef8e3bcf6b83cdf53d967a Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:01:36 +0200 Subject: [PATCH 10/25] Added damage saving throws to engine --- .../applyPropertyByType/applyAction.js | 6 +- .../applyPropertyByType/applyDamage.js | 76 +++++++++++++++++-- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 115d1998..070cef04 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -174,7 +174,11 @@ function rollAttack(attack, scope) { } function applyCrits(value, scope) { - const criticalHitTarget = scope['~criticalHitTarget']?.value || 20; + let scopeCrit = scope['~criticalHitTarget']?.value; + if (scopeCrit?.parseType === 'constant') { + scopeCrit = scopeCrit.value; + } + const criticalHitTarget = scopeCrit || 20; let criticalHit = value >= criticalHitTarget; let criticalMiss; if (criticalHit) { diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index b1923a4d..3cbe225c 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -10,6 +10,7 @@ import { } from '/imports/api/engine/loadCreatures.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js'; +import applySavingThrow from '/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js'; export default function applyDamage(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); @@ -36,7 +37,7 @@ export default function applyDamage(node, actionContext) { const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage'; // roll the dice only and store that string - applyEffectsToCalculationParseNode(prop.amount, actionContext.log); + applyEffectsToCalculationParseNode(prop.amount, actionContext); const { result: rolled } = resolve('roll', prop.amount.parseNode, scope, context); if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); @@ -67,6 +68,7 @@ export default function applyDamage(node, actionContext) { // Round the damage to a whole number damage = Math.floor(damage); + scope['~damage'] = damage; // Convert extra damage into the stored type if (prop.damageType === 'extra' && scope['~lastDamageType']?.value) { @@ -82,24 +84,74 @@ export default function applyDamage(node, actionContext) { prop.damageType + (prop.damageType !== 'healing' ? ' damage ' : ''); + // If there is a save, calculate the save damage + let damageOnSave, saveNode, saveRoll; + if (prop.save) { + if (prop.save.damageFunction?.calculation) { + applyEffectsToCalculationParseNode(prop.save.damageFunction, actionContext); + let { result: saveDamageRolled } = resolve('roll', prop.save.damageFunction.parseNode, scope, context); + saveRoll = toString(saveDamageRolled); + let { result: saveDamageResult } = resolve('reduce', saveDamageRolled, scope, context); + // If we didn't end up with a constant of finite amount, give up + if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) { + return applyChildren(node, actionContext); + } + damageOnSave = +saveDamageResult.value; + // Round the damage to a whole number + damageOnSave = Math.floor(damageOnSave); + } else { + damageOnSave = Math.floor(damage / 2); + } + saveNode = { + node: { + ...prop.save, + name: prop.save.stat, + silent: prop.silent, + }, + children: [], + } + } + if (damageTargets && damageTargets.length) { // Iterate through all the targets damageTargets.forEach(target => { + actionContext.target = [target]; + let damageToApply = damage; + + // If there is a saving throw, apply that first + if (prop.save) { + applySavingThrow(saveNode, actionContext); + if (scope['~saveSucceeded']?.value) { + // Log the total damage + logValue.push(toString(reduced)); + // Log the save damage + const damageText = damageFunctionText(prop.save); + if (damageText) { + logValue.push(damageText); + } else { + logValue.push( + '**Damage on successful save**', + prop.save.damageFunction.calculation, + saveRoll + ); + } + damageToApply = damageOnSave; + } + } // Apply weaknesses/resistances/immunities - damage = applyDamageMultipliers({ + damageToApply = applyDamageMultipliers({ target, - damage, + damage: damageToApply, damageProp: prop, logValue }); - actionContext.target = [target]; // Deal the damage to the target let damageDealt = dealDamage({ target, damageType: prop.damageType, - amount: damage, + amount: damageToApply, actionContext }); @@ -124,6 +176,10 @@ export default function applyDamage(node, actionContext) { } else { // There are no targets, just log the result logValue.push(`**${damage}** ${suffix}`); + if (prop.save) { + applySavingThrow(saveNode, actionContext); + logValue.push(`**${damageOnSave}** ${suffix} on a successful save`); + } } if (!prop.silent) actionContext.addLog({ name: logName, @@ -133,6 +189,16 @@ export default function applyDamage(node, actionContext) { return applyChildren(node, actionContext); } +function damageFunctionText(save, scope, context, actionContext) { + if (!save) return []; + if (!save.damageFunction) { + return '**Half damage on successful save**'; + } + if (save.damageFunction.calculation == '0' || save.damageFunction.value === 0) { + return '**No damage on successful save**' + } +} + function applyDamageMultipliers({ target, damage, damageProp, logValue }) { const damageType = damageProp?.damageType; if (!damageType) return damage; From 98ac2e7122937b0d0d0671ced3b5efbfb0419db3 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:10:32 +0200 Subject: [PATCH 11/25] Made ammo consumed available in scope as `~ammo` --- .../api/engine/actions/applyPropertyByType/applyAction.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 070cef04..433d4ec1 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -243,6 +243,9 @@ function spendResources(prop, actionContext) { gainLog.push(logName + ': ' + -itemConsumed.quantity.value); } ammoChildren.push(...getItemChildren(item, actionContext, prop)); + // simulate the increment and add the item to the action scope + item.quantity -= itemConsumed.quantity.value; + actionContext.scope['~ammo'] = item; }); } catch (e) { actionContext.addLog({ From 9d833a1fe301ec6015a22a81e25e3ff3680ba079 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:48:48 +0200 Subject: [PATCH 12/25] Removed ammo as ~ammo, items should be applied #ammo will be the way to referenced last used ammo --- .../api/engine/actions/applyPropertyByType/applyAction.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 433d4ec1..4cdb7d69 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -243,9 +243,14 @@ function spendResources(prop, actionContext) { gainLog.push(logName + ': ' + -itemConsumed.quantity.value); } ammoChildren.push(...getItemChildren(item, actionContext, prop)); + /* Disabled this for now. Applying an item as ammo should be its own "applyPropertyByType" + * which will set #item correctly before applying the item's children + * and make triggers compatible in the future + // simulate the increment and add the item to the action scope item.quantity -= itemConsumed.quantity.value; actionContext.scope['~ammo'] = item; + */ }); } catch (e) { actionContext.addLog({ From 3a3deca867c5febd4a81d9fa8b316086a6bdff1c Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:51:15 +0200 Subject: [PATCH 13/25] Removed `x not found, set to 0` info messages from parser --- app/imports/parser/parseTree/accessor.js | 15 +++++------ app/imports/parser/parseTree/symbol.js | 34 +++++++++++------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/app/imports/parser/parseTree/accessor.js b/app/imports/parser/parseTree/accessor.js index 920e1e6b..5ca0e325 100644 --- a/app/imports/parser/parseTree/accessor.js +++ b/app/imports/parser/parseTree/accessor.js @@ -3,7 +3,7 @@ import constant from './constant.js'; import { toString } from '../resolve.js'; const accessor = { - create({name, path}) { + create({ name, path }) { return { parseType: 'accessor', path, @@ -19,12 +19,12 @@ const accessor = { }); let valueType = Array.isArray(value) ? 'array' : typeof value; // If the accessor returns an objet, get the object's value instead - while (valueType === 'object'){ + while (valueType === 'object') { value = value.value; valueType = Array.isArray(value) ? 'array' : typeof value; } // Return a parse node based on the type returned - if (valueType === 'string' || valueType === 'number' || valueType === 'boolean'){ + if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { return { result: constant.create({ value, @@ -65,10 +65,9 @@ const accessor = { }; } }, - reduce(node, scope, context){ + reduce(node, scope, context) { let { result } = accessor.compile(node, scope, context); - if (result.parseType === 'accessor'){ - context.error(`${toString(result)} not found, set to 0`); + if (result.parseType === 'accessor') { return { result: constant.create({ value: 0, @@ -76,10 +75,10 @@ const accessor = { context }; } else { - return {result, context}; + return { result, context }; } }, - toString(node){ + toString(node) { return `${node.name}.${node.path.join('.')}`; } } diff --git a/app/imports/parser/parseTree/symbol.js b/app/imports/parser/parseTree/symbol.js index 16ec486b..cdad5757 100644 --- a/app/imports/parser/parseTree/symbol.js +++ b/app/imports/parser/parseTree/symbol.js @@ -2,58 +2,54 @@ import resolve, { toString } from '../resolve.js'; import constant from './constant.js'; const symbol = { - create({name}){ + create({ name }) { return { parseType: 'symbol', name, }; }, - toString(node){ + toString(node) { return `${node.name}` }, - compile(node, scope, context, calledFromReduce = false){ + compile(node, scope, context, calledFromReduce = false) { let value = scope && scope[node.name]; let type = typeof value; // For objects, default to their .value - if (type === 'object'){ + if (type === 'object') { value = value.value; type = typeof value; } // For parse nodes, compile and return - if (value?.parseType){ - if (calledFromReduce){ + if (value?.parseType) { + if (calledFromReduce) { return resolve('reduce', value, scope, context); } else { return resolve('compile', value, scope, context); } } - if (type === 'string' || type === 'number' || type === 'boolean'){ + if (type === 'string' || type === 'number' || type === 'boolean') { return { - result: constant.create({value}), + result: constant.create({ value }), context, }; - } else if (type === 'undefined'){ + } else if (type === 'undefined') { return { - result: symbol.create({name: node.name}), + result: symbol.create({ name: node.name }), context, }; } else { throw new Meteor.Error(`Unexpected case: ${node.name} resolved to ${value}`); } }, - reduce(node, scope, context){ - let {result} = symbol.compile(node, scope, context, true); - if (result.parseType === 'symbol'){ - context.error({ - type: 'info', - message: `${toString(result)} not found, set to 0` - }); + reduce(node, scope, context) { + let { result } = symbol.compile(node, scope, context, true); + if (result.parseType === 'symbol') { return { - result: constant.create({value: 0}), + result: constant.create({ value: 0 }), context, }; } else { - return {result, context}; + return { result, context }; } } } From d3c533dfa11291efd31765f0f60f4868eedaadd2 Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:03:37 +0200 Subject: [PATCH 14/25] Moved ammo to its own pseudo property Triggers now work on ammo #amo now works as well --- .../api/engine/actions/applyProperty.js | 2 + .../applyPropertyByType/applyAction.js | 57 +++++++------------ .../applyPropertyByType/applyItemAsAmmo.js | 42 ++++++++++++++ app/imports/api/properties/Items.js | 5 ++ app/imports/api/properties/Triggers.js | 1 + .../client/ui/properties/forms/ItemForm.vue | 30 ++++++++-- 6 files changed, 94 insertions(+), 43 deletions(-) create mode 100644 app/imports/api/engine/actions/applyPropertyByType/applyItemAsAmmo.js diff --git a/app/imports/api/engine/actions/applyProperty.js b/app/imports/api/engine/actions/applyProperty.js index 18f35d32..b8e809d8 100644 --- a/app/imports/api/engine/actions/applyProperty.js +++ b/app/imports/api/engine/actions/applyProperty.js @@ -1,4 +1,5 @@ import action from './applyPropertyByType/applyAction.js'; +import ammo from './applyPropertyByType/applyItemAsAmmo.js' import adjustment from './applyPropertyByType/applyAdjustment.js'; import branch from './applyPropertyByType/applyBranch.js'; import buff from './applyPropertyByType/applyBuff.js'; @@ -12,6 +13,7 @@ import toggle from './applyPropertyByType/applyToggle.js'; const applyPropertyByType = { action, + ammo, adjustment, branch, buff, diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 4cdb7d69..b44c1b44 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -4,13 +4,10 @@ import rollDice from '/imports/parser/rollDice.js'; import applyProperty from '../applyProperty.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; -import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; import numberToSignedString from '/imports/api/utility/numberToSignedString.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js'; -import { getPropertyDecendants } from '/imports/api/engine/loadCreatures.js'; -import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'; export default function applyAction(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); @@ -210,10 +207,9 @@ function spendResources(prop, actionContext) { return true; } // Items - let itemQuantityAdjustments = []; let spendLog = []; let gainLog = []; - let ammoChildren = []; + const ammoToApply = []; try { prop.resources.itemsConsumed.forEach(itemConsumed => { recalculateCalculation(itemConsumed.quantity, actionContext); @@ -228,11 +224,6 @@ function spendResources(prop, actionContext) { !itemConsumed?.quantity?.value || !isFinite(itemConsumed.quantity.value) ) return; - itemQuantityAdjustments.push({ - property: item, - operation: 'increment', - value: itemConsumed.quantity.value, - }); let logName = item.name; if (itemConsumed.quantity.value > 1 || itemConsumed.quantity.value < -1) { logName = item.plural || logName; @@ -242,15 +233,20 @@ function spendResources(prop, actionContext) { } else if (itemConsumed.quantity.value < 0) { gainLog.push(logName + ': ' + -itemConsumed.quantity.value); } - ammoChildren.push(...getItemChildren(item, actionContext, prop)); - /* Disabled this for now. Applying an item as ammo should be its own "applyPropertyByType" - * which will set #item correctly before applying the item's children - * and make triggers compatible in the future - - // simulate the increment and add the item to the action scope - item.quantity -= itemConsumed.quantity.value; - actionContext.scope['~ammo'] = item; - */ + // So long as the item isn't an ancestor of the current prop apply it + // If it was an ancestor this would be an infinite loop + if (!hasAncestorRelationship(item, prop)) { + ammoToApply.push({ + node: { + ...item, + // Use ammo pseudo-type + type: 'ammo', + // Store the adjustment to be applied + adjustment: itemConsumed.quantity.value, + }, + children: [] + }); + } }); } catch (e) { actionContext.addLog({ @@ -261,9 +257,6 @@ function spendResources(prop, actionContext) { return true; } // No more errors should be thrown after this line - // Now that we have confirmed that there are no errors, do actual work - //Items - itemQuantityAdjustments.forEach(adjustQuantityWork); // Use uses if (prop.usesLeft) { @@ -303,6 +296,11 @@ function spendResources(prop, actionContext) { } }); + // Apply the ammo children + ammoToApply.forEach(node => { + applyProperty(node, actionContext); + }); + // Log all the spending if (gainLog.length && !prop.silent) actionContext.addLog({ name: 'Gained', @@ -314,21 +312,6 @@ function spendResources(prop, actionContext) { value: spendLog.join('\n'), inline: true, }); - - // Apply the ammo children - ammoChildren.forEach(prop => { - applyProperty(prop, actionContext); - }); -} - -function getItemChildren(item, actionContext, prop) { - // Skip if the prop or the item are ancestors of one another, otherwise infinite loop - if (hasAncestorRelationship(item, prop)) return []; - // Get the item children - const itemProperties = getPropertyDecendants(actionContext.creature._id, item._id); - // Tree them up - const propertyForest = nodeArrayToTree(itemProperties); - return propertyForest } function hasAncestorRelationship(a, b) { diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyItemAsAmmo.js b/app/imports/api/engine/actions/applyPropertyByType/applyItemAsAmmo.js new file mode 100644 index 00000000..3bc3b24e --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyItemAsAmmo.js @@ -0,0 +1,42 @@ +import { getPropertyDecendants } from '/imports/api/engine/loadCreatures.js'; +import applyProperty from '../applyProperty.js'; +import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'; +import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js'; + +export default function applyItemAsAmmo(node, actionContext) { + // The item node should come without children, since it is not part of the original action tree + const prop = node.node; + // Get all the item's descendant properties + const properties = getPropertyDecendants(actionContext.creature._id, prop._id); + properties.sort((a, b) => a.order - b.order); + const propertyForest = nodeArrayToTree(properties); + + // Apply the item + applyNodeTriggers(node, 'before', actionContext); + + // Do the quantity adjustment + const itemProp = { ...prop, type: 'item' }; + delete itemProp.adjustment; + adjustQuantityWork({ + property: itemProp, + operation: 'increment', + value: prop.adjustment, + }); + + // Simulate the change to quantity + prop.quantity -= prop.adjustment; + + // Log the item name as a heading if it's not silent and has child properties to apply + if (!prop.silent && propertyForest.length) { + actionContext.addLog({ + name: prop.name || 'Ammo', + inline: false, + }); + } + applyNodeTriggers(node, 'after', actionContext); + + // Apply the item's children + propertyForest.forEach(node => applyProperty(node, actionContext)); + applyNodeTriggers(node, 'afterChildren', actionContext); +} diff --git a/app/imports/api/properties/Items.js b/app/imports/api/properties/Items.js index a328cfcf..b4bb7319 100644 --- a/app/imports/api/properties/Items.js +++ b/app/imports/api/properties/Items.js @@ -55,6 +55,11 @@ const ItemSchema = createPropertySchema({ type: Boolean, defaultValue: false, }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); let ComputedOnlyItemSchema = createPropertySchema({ diff --git a/app/imports/api/properties/Triggers.js b/app/imports/api/properties/Triggers.js index 250d6bda..09d777e2 100644 --- a/app/imports/api/properties/Triggers.js +++ b/app/imports/api/properties/Triggers.js @@ -23,6 +23,7 @@ const timingOptions = { const actionPropertyTypeOptions = { action: 'Action', + ammo: 'Ammo used', adjustment: 'Attribute damage', branch: 'Branch', buff: 'Buff', diff --git a/app/imports/client/ui/properties/forms/ItemForm.vue b/app/imports/client/ui/properties/forms/ItemForm.vue index 3bbebdd4..394ccec0 100644 --- a/app/imports/client/ui/properties/forms/ItemForm.vue +++ b/app/imports/client/ui/properties/forms/ItemForm.vue @@ -84,12 +84,30 @@ - + + + + + + + + Date: Wed, 20 Sep 2023 15:42:40 +0200 Subject: [PATCH 15/25] Fixed empty calculations unable to be targeted by effects --- .../buildComputation/linkTypeDependencies.js | 6 +-- .../parseCalculationFields.js | 39 +++++++++++++++---- .../computation/buildCreatureComputation.js | 1 + .../computation/computeCreatureComputation.js | 21 +++++----- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index a3d65599..10ce0b0a 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -159,7 +159,7 @@ function linkEffects(dependencyGraph, prop, computation) { // Otherwise target a field on that property const key = prop.targetField || getDefaultCalculationField(targetProp); const calcObj = get(targetProp, key); - if (calcObj && calcObj.calculation) { + if (calcObj) { dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'effect'); } } @@ -301,7 +301,7 @@ function linkProficiencies(dependencyGraph, prop, computation) { // Otherwise target a field on that property const key = prop.targetField || getDefaultCalculationField(targetProp); const calcObj = get(targetProp, key); - if (calcObj && calcObj.calculation) { + if (calcObj) { dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency'); } } @@ -339,7 +339,7 @@ function linkSkill(dependencyGraph, prop, computation) { // other skill isn't supported const key = prop.targetField || getDefaultCalculationField(targetProp); const calcObj = get(targetProp, key); - if (calcObj && calcObj.calculation) { + if (calcObj) { dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency'); } }); diff --git a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js index 73217f80..6ef2b165 100644 --- a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js +++ b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js @@ -1,7 +1,7 @@ import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; import { prettifyParseError, parse } from '/imports/parser/parser.js'; import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js'; -import { get, unset } from 'lodash'; +import { get, set, unset } from 'lodash'; import errorNode from '/imports/parser/parseTree/error.js'; import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js'; @@ -63,12 +63,21 @@ function parseAllCalculationFields(prop, schemas) { // For all fields matching they keys // supports `keys.$.with.$.arrays` applyFnToKey(prop, calcKey, (prop, key) => { - const calcObj = get(prop, key); + let calcObj = get(prop, key); + // Create a calculation object if one doesn't exist, it will get deleted again later if + // it's not used, but if an effect targets a calculated field, we should have one to target + if ( + !calcObj + && subDocsExist(prop, key) + ) { + calcObj = {}; + set(prop, key, calcObj); + } + // Sub document didn't exist, skip this field if (!calcObj) return; - // Delete the whole calculation object if the calculation string isn't set + // Keep a list of empty calculations for potential deletion if they aren't used if (!calcObj.calculation) { - unset(prop, calcKey); - return; + prop._computationDetails.emptyCalculations.push(calcObj); } // Store a reference to all the calculations prop._computationDetails.calculations.push(calcObj); @@ -84,15 +93,31 @@ function parseAllCalculationFields(prop, schemas) { }); } +function subDocsExist(prop, key) { + const path = key.split('.'); + if (path.length < 2) return !!prop; + path.pop(); + const subPath = path.join('.'); + return !!get(prop, subPath); +} + +export function removeEmptyCalculations(prop) { + prop._computationDetails.emptyCalculations.forEach(calcObj => { + if (!calcObj.effects?.length) { + unset(prop, calcObj._key); + } + }); +} + function parseCalculation(calcObj) { - const calcHash = cyrb53(calcObj.calculation); + const calcHash = cyrb53(calcObj.calculation || '0'); // If the cached parse calculation is equal to the calculation, skip if (calcHash === calcObj.hash) { return; } calcObj.hash = calcHash; try { - calcObj.parseNode = parse(calcObj.calculation); + calcObj.parseNode = parse(calcObj.calculation || '0'); calcObj.parseError = null; } catch (e) { let error = { diff --git a/app/imports/api/engine/computation/buildCreatureComputation.js b/app/imports/api/engine/computation/buildCreatureComputation.js index a97f5b0a..e9dbb625 100644 --- a/app/imports/api/engine/computation/buildCreatureComputation.js +++ b/app/imports/api/engine/computation/buildCreatureComputation.js @@ -75,6 +75,7 @@ export function buildComputationFromProps(properties, creature, variables) { // Add a place to store all the computation details prop._computationDetails = { calculations: [], + emptyCalculations: [], inlineCalculations: [], toggleAncestors: [], }; diff --git a/app/imports/api/engine/computation/computeCreatureComputation.js b/app/imports/api/engine/computation/computeCreatureComputation.js index 7cd4b41c..1ffe58be 100644 --- a/app/imports/api/engine/computation/computeCreatureComputation.js +++ b/app/imports/api/engine/computation/computeCreatureComputation.js @@ -1,9 +1,10 @@ import computeToggles from '/imports/api/engine/computation/computeComputation/computeToggles.js'; import computeByType from '/imports/api/engine/computation/computeComputation/computeByType.js'; import embedInlineCalculations from './utility/embedInlineCalculations.js'; +import { removeEmptyCalculations } from './buildComputation/parseCalculationFields.js'; import path from 'ngraph.path'; -export default function computeCreatureComputation(computation){ +export default function computeCreatureComputation(computation) { const stack = []; // Computation scope of {variableName: prop} const graph = computation.dependencyGraph; @@ -20,16 +21,16 @@ export default function computeCreatureComputation(computation){ stack.reverse(); // Depth first traversal of nodes - while (stack.length){ + while (stack.length) { let top = stack[stack.length - 1]; - if (top._visited){ + if (top._visited) { // The object has already been computed, skip stack.pop(); - } else if (top._visitedChildren){ + } else if (top._visitedChildren) { // Mark the object as visited and remove from stack top._visited = true; stack.pop(); - // Compute the top object of the stack + // Compute the top object of the stack compute(computation, top); } else { top._visitedChildren = true; @@ -42,14 +43,14 @@ export default function computeCreatureComputation(computation){ computation.props.forEach(finalizeProp); } -function compute(computation, node){ +function compute(computation, node) { // Determine the prop's active status by its toggles computeToggles(computation, node); // Compute the property by type computeByType[node.data?.type || '_variable']?.(computation, node); } -function pushDependenciesToStack(nodeId, graph, stack, computation){ +function pushDependenciesToStack(nodeId, graph, stack, computation) { graph.forEachLinkedNode(nodeId, linkedNode => { if (linkedNode._visitedChildren && !linkedNode._visited) { // This is a dependency loop, find a path from the node to itself @@ -66,7 +67,7 @@ function pushDependenciesToStack(nodeId, graph, stack, computation){ loop = [linkedNode, ...newLoop]; } }, true); - + if (loop.length) { computation.errors.push({ type: 'dependencyLoop', @@ -80,11 +81,13 @@ function pushDependenciesToStack(nodeId, graph, stack, computation){ }, true); } -function finalizeProp(prop){ +function finalizeProp(prop) { // Embed the inline calculations prop._computationDetails?.inlineCalculations?.forEach(inlineCalcObj => { embedInlineCalculations(inlineCalcObj); }); + // Clean up the calculations that were never used + removeEmptyCalculations(prop); // Clean up the computation details delete prop._computationDetails; } From 7754482da7da65b06551ad4e1cf103bfe70a827c Mon Sep 17 00:00:00 2001 From: Thaum Rystra <9525416+ThaumRystra@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:43:16 +0200 Subject: [PATCH 16/25] Fixed damage tree node not rendering if it has no amount --- .../treeNodeViews/DamageTreeNode.vue | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/imports/client/ui/properties/treeNodeViews/DamageTreeNode.vue b/app/imports/client/ui/properties/treeNodeViews/DamageTreeNode.vue index c4502817..b81788f7 100644 --- a/app/imports/client/ui/properties/treeNodeViews/DamageTreeNode.vue +++ b/app/imports/client/ui/properties/treeNodeViews/DamageTreeNode.vue @@ -1,6 +1,9 @@ @@ -37,8 +45,8 @@ import { getPropertyIcon } from '/imports/constants/PROPERTIES.js'; import InlineEffect from '../components/effects/InlineEffect.vue'; export default { - mixins: [treeNodeViewMixin], components: {InlineEffect}, + mixins: [treeNodeViewMixin], computed: { icon() { if (this.model.damageType === 'healing') { From 572078c2faee6af550e711eb7a32a7520e470127 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:53:39 +0200 Subject: [PATCH 17/25] Fixed dependency loop when props target self by tags --- .../engine/computation/buildComputation/linkTypeDependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 10ce0b0a..5a8a5dc0 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -175,7 +175,7 @@ function linkEffects(dependencyGraph, prop, computation) { // Returns an array of IDs of the properties the effect targets export function getEffectTagTargets(effect, computation) { let targets = getTargetListFromTags(effect.targetTags, computation); - let notIds = []; + let notIds = [effect._id]; // Can't target itself if (effect.extraTags) { effect.extraTags.forEach(ex => { if (ex.operation === 'OR') { From bfbb31d30cac0b0ad338df22513216d10eaf2701 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:45:40 +0200 Subject: [PATCH 18/25] Fixed performance regression in dependency graph speed --- .../buildComputation/linkCalculationDependencies.js | 9 ++++++++- .../buildComputation/linkTypeDependencies.js | 12 ++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js index 68a19951..35ba9b60 100644 --- a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js @@ -9,8 +9,15 @@ export default function linkCalculationDependencies(dependencyGraph, prop, { pro }; // Add this calculation to the dependency graph const calcNodeId = `${prop._id}.${calcObj._key}`; - dependencyGraph.addNode(calcNodeId, calcObj); + // Skip empty calculations that aren't targeted by anything + if ( + !calcObj.calculation + && !calcObj.effects + && !calcObj.proficiencies + ) return; + + dependencyGraph.addNode(calcNodeId, calcObj); // Traverse the parsed calculation looking for variable names traverse(calcObj.parseNode, node => { // Skip nodes that aren't symbols or accessors diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 5a8a5dc0..814449c3 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -98,8 +98,10 @@ function linkAdjustment(dependencyGraph, prop) { function linkAttribute(dependencyGraph, prop) { linkVariableName(dependencyGraph, prop); - // Depends on spellSlotLevel - dependOnCalc({ dependencyGraph, prop, key: 'spellSlotLevel' }); + // Spell slots depend on spellSlotLevel + if (prop.type === 'spellSlot') { + dependOnCalc({ dependencyGraph, prop, key: 'spellSlotLevel' }); + } // Depends on base value dependOnCalc({ dependencyGraph, prop, key: 'baseValue' }); @@ -257,8 +259,8 @@ function linkDamageMultiplier(dependencyGraph, prop) { function linkPointBuy(dependencyGraph, prop) { dependOnCalc({ dependencyGraph, prop, key: 'min' }); dependOnCalc({ dependencyGraph, prop, key: 'max' }); - dependOnCalc({ dependencyGraph, prop, key: 'cost' }); dependOnCalc({ dependencyGraph, prop, key: 'total' }); + prop.values?.forEach((row, index) => { // Get a unique id for the row because it might be shared among duplicated point buy tables // prop._id is forced unique by the database, so it can be used instead @@ -273,9 +275,7 @@ function linkPointBuy(dependencyGraph, prop) { } dependencyGraph.addNode(pointBuyRow._id, pointBuyRow); linkVariableName(dependencyGraph, pointBuyRow); - dependOnCalc({ dependencyGraph, pointBuyRow, key: 'min' }); - dependOnCalc({ dependencyGraph, pointBuyRow, key: 'max' }); - dependOnCalc({ dependencyGraph, pointBuyRow, key: 'cost' }); + dependencyGraph.addLink(pointBuyRow._id, prop._id, 'pointBuyRow'); }); if (prop.inactive) return; } From 745296c1db69130fcab4e99a7c1e395b24e54be9 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:46:14 +0200 Subject: [PATCH 19/25] Fixed regression: point buy cost calc failing --- .../computeComputation/computeByType/computePointBuy.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js index c2cc7606..a7727247 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js @@ -3,8 +3,8 @@ import evaluateCalculation from '../../utility/evaluateCalculation.js'; export default function computePointBuy(computation, node) { const prop = node.data; - const tableMin = prop.min?.value || null; - const tableMax = prop.max?.value || null; + const min = has(prop, 'min.value') ? prop.min.value : null; + const max = has(prop, 'max.value') ? prop.max.value : null; prop.spent = 0; prop.values?.forEach(row => { // Clean up added properties @@ -14,9 +14,7 @@ export default function computePointBuy(computation, node) { row.spent = 0; if (row.value === undefined) return; - const min = has(row, 'min.value') ? row.min.value : tableMin; - const max = has(row, 'max.value') ? row.max.value : tableMax; - const costFunction = EJSON.clone(row.cost || prop.cost); + const costFunction = EJSON.clone(prop.cost); if (costFunction) costFunction.parseLevel = 'reduce'; // Check min and max From ad15020f0bea0cbaea801cfbb0ae36fcfd51fb54 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:46:26 +0200 Subject: [PATCH 20/25] Removed per-row point buy cost/min/max --- app/imports/api/properties/PointBuys.js | 27 +------------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/app/imports/api/properties/PointBuys.js b/app/imports/api/properties/PointBuys.js index db20911a..fcbb8f6e 100644 --- a/app/imports/api/properties/PointBuys.js +++ b/app/imports/api/properties/PointBuys.js @@ -29,7 +29,7 @@ let PointBuySchema = createPropertySchema({ 'values.$._id': { type: String, regEx: SimpleSchema.RegEx.Id, - autoValue(){ + autoValue() { if (!this.isSet) return Random.id(); } }, @@ -49,18 +49,6 @@ let PointBuySchema = createPropertySchema({ type: Number, optional: true, }, - 'values.$.min': { - type: 'fieldToCompute', - optional: true, - }, - 'values.$.max': { - type: 'fieldToCompute', - optional: true, - }, - 'values.$.cost': { - type: 'fieldToCompute', - optional: true, - }, min: { type: 'fieldToCompute', optional: true, @@ -102,19 +90,6 @@ const ComputedOnlyPointBuySchema = createPropertySchema({ 'values.$': { type: Object, }, - 'values.$.min': { - type: 'computedOnlyField', - optional: true, - }, - 'values.$.max': { - type: 'computedOnlyField', - optional: true, - }, - 'values.$.cost': { - type: 'computedOnlyField', - optional: true, - parseLevel: 'compile', - }, 'values.$.spent': { type: Number, optional: true, From df8f9c085f4b499edbcf6519be3c631f01cc4773 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:52:21 +0200 Subject: [PATCH 21/25] Fixed regression in actions breaking ui and uses --- .../computeComputation/computeByType/computeAction.js | 2 +- app/imports/client/ui/properties/viewers/ActionViewer.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index a8243f40..48a71fb5 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -1,6 +1,6 @@ export default function computeAction(computation, node) { const prop = node.data; - if (prop.uses) { + if (Number.isFinite(prop.uses?.value)) { prop.usesLeft = prop.uses.value - (prop.usesUsed || 0); if (!prop.usesLeft) { prop.insufficientResources = true; diff --git a/app/imports/client/ui/properties/viewers/ActionViewer.vue b/app/imports/client/ui/properties/viewers/ActionViewer.vue index 2cea866a..de20f2fe 100644 --- a/app/imports/client/ui/properties/viewers/ActionViewer.vue +++ b/app/imports/client/ui/properties/viewers/ActionViewer.vue @@ -65,7 +65,7 @@ :value="reset" />
From 643e7892c8139c5b84c32d3d4491fbd2ebcdbe9f Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Fri, 22 Sep 2023 22:10:11 +0200 Subject: [PATCH 22/25] Minor redesign of printed character sheets --- .../CharacterSheetPrinted.vue | 35 +++- .../PrintedInventory.vue | 131 +++++++-------- .../printedCharacterSheet/PrintedStats.vue | 154 +++++++++--------- .../components/PrintedAction.vue | 118 +++++++++----- .../{PrintedItem.vue => PrintedBlockItem.vue} | 0 .../components/PrintedLineItem.vue | 122 ++++++++++++++ .../components/PrintedSkill.vue | 4 +- 7 files changed, 366 insertions(+), 198 deletions(-) rename app/imports/client/ui/creature/character/printedCharacterSheet/components/{PrintedItem.vue => PrintedBlockItem.vue} (100%) create mode 100644 app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedLineItem.vue diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue index 1d47cd09..61cc7628 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue @@ -32,7 +32,10 @@ light >
-
+
{{ creature.name }} @@ -59,7 +62,7 @@
{{ creatureUrl }}
@@ -234,7 +237,7 @@ export default { .character-sheet-printed { background: white; color: black; - font-size: 11pt; + font-size: 10pt; } .character-sheet-printed * { @@ -247,6 +250,14 @@ export default { padding: 4px; } +.character-sheet-printed p { + margin-bottom: 8px; +} + +.character-sheet-printed .double-border > .label:first-child { + margin-bottom: 8px; +} + .character-sheet-printed .column-layout, .character-sheet-printed .column-layout.wide-columns { position:relative; width: 100%; @@ -256,6 +267,10 @@ export default { column-fill: balance-all; } +.character-sheet-printed .column-layout { + column-width: 200px; +} + .character-sheet-printed .column-layout>div { position:relative; } @@ -267,9 +282,10 @@ export default { opacity: 1 !important; } .character-sheet-printed .creature-name { - font-size: 24pt; + font-size: 16pt; background-color: white; } + .character-sheet-printed .logo-background { width: 60px; height: 60px; @@ -284,6 +300,10 @@ export default { max-width: unset; } +.character-sheet-printed .tree-node-title { + min-height: unset !important; +} + .character-sheet-printed .double-border { position: relative; padding: 11px 10px; @@ -291,6 +311,8 @@ export default { border-image-slice: 110 126 fill; border-image-width: 16px; border-image-repeat: stretch; + box-decoration-break: clone; + page-break-inside: avoid; } .character-sheet-printed .octagon-border { @@ -298,6 +320,8 @@ export default { padding: 4px 20px; border-image: url(/images/print/octagonBorder.png) 124 118 fill; border-image-width: 22px; + box-decoration-break: clone; + page-break-inside: avoid; } .character-sheet-printed .span-all { @@ -322,6 +346,7 @@ export default { .character-sheet-printed .span-all { column-span: all; + display: block; } @media screen { @@ -337,7 +362,7 @@ export default { @media print { @page { size: auto; - margin: 8mm 8mm 8mm 8mm; + margin: 8mm; } body { margin: 0; diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue index 416e8b00..6a6c97c3 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue @@ -2,83 +2,72 @@
- -
-
-
- Inventory -
-
- $vuetify.icons.injustice - Weight Carried: - {{ weightCarried }} lb -
-
- $vuetify.icons.cash - Net worth: - -
-
- $vuetify.icons.spell - Items attuned: - {{ variables.itemsAttuned && variables.itemsAttuned.value }} -
-
+
+
+ Inventory
-
-
- Equipped -
+
+ $vuetify.icons.injustice + Weight Carried: + {{ weightCarried }} lb
-
- + $vuetify.icons.cash + Net worth: +
-
-
- Carried -
-
+ $vuetify.icons.spell + Items attuned: + {{ variables.itemsAttuned && variables.itemsAttuned.value }} +
+
+
+
+ Equipped +
+ + +
+
+
+ Carried
- - + :model="item" + /> + +
@@ -90,7 +79,7 @@ import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/ import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js'; import CoinValue from '/imports/client/ui/components/CoinValue.vue'; import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js'; -import PrintedItem from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue'; +import PrintedLineItem from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedLineItem.vue'; import PrintedContainer from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue'; import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js'; @@ -98,7 +87,7 @@ export default { components: { ColumnLayout, CoinValue, - PrintedItem, + PrintedItem: PrintedLineItem, PrintedContainer, }, props: { @@ -233,15 +222,17 @@ export default { \ No newline at end of file diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue index 404da2aa..a1765b98 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue @@ -11,7 +11,7 @@ :value="model.proficiency" class="prof-icon" /> -
+
{{ displayedModifier }}
.printed-skill{ - min-height: 30px; + min-height: 0; } .prof-icon { From 6204be2240e7f47bba1ccf1e306d5bdc5dbf40bc Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Sat, 23 Sep 2023 12:47:31 +0200 Subject: [PATCH 23/25] Fixed printing on Chrome --- .../CharacterSheetPrinted.vue | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue index 61cc7628..c4e5e2ab 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue @@ -67,9 +67,13 @@ {{ creatureUrl }}
- +
@@ -263,8 +267,8 @@ export default { width: 100%; widows: 0; orphans: 0; - -webkit-column-fill: balance-all; - column-fill: balance-all; + column-fill: balance; + padding: 0; } .character-sheet-printed .column-layout { @@ -273,6 +277,8 @@ export default { .character-sheet-printed .column-layout>div { position:relative; + margin-top: 4px; + margin-bottom: 4px; } .character-sheet-printed .column-layout > div > * { page-break-inside: avoid; @@ -306,7 +312,8 @@ export default { .character-sheet-printed .double-border { position: relative; - padding: 11px 10px; + border-style: solid; + border-width: 11px 10px; border-image-source: url(/images/print/doubleLineImageBorder.png); border-image-slice: 110 126 fill; border-image-width: 16px; @@ -335,7 +342,7 @@ export default { .character-sheet-printed .stats .label { font-size: 10pt; - font-variant: small-caps; + font-variant: all-small-caps } .character-sheet-printed .label { @@ -349,6 +356,14 @@ export default { display: block; } +.character-sheet-printed .page-break-before { + page-break-before: always; +} + +.character-sheet-printed .avoid-page-break-after { + page-break-after: avoid; +} + @media screen { .character-sheet-printed { display: flex; From ac8bd2cddb0cf1938952d7fc912339d8ee52c929 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Sat, 23 Sep 2023 12:48:05 +0200 Subject: [PATCH 24/25] Iterated on printing format --- .../printedCharacterSheet/PrintedSpells.vue | 39 +++---- .../printedCharacterSheet/PrintedStats.vue | 106 ++++++++++-------- .../components/PrintedAction.vue | 2 +- .../components/PrintedContainer.vue | 5 - .../components/PrintedLineItem.vue | 9 +- .../components/PrintedSpell.vue | 82 ++++++++++---- .../components/PrintedSpellList.vue | 5 +- app/imports/client/ui/utility/romanize.js | 31 +++++ 8 files changed, 182 insertions(+), 97 deletions(-) create mode 100644 app/imports/client/ui/utility/romanize.js diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue index 5e547734..cac6e162 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue @@ -2,37 +2,38 @@
- -
-
- Spells -
-
+
+ Spells +
+
- -
+
+
diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue index b504b5a6..85832259 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue @@ -13,7 +13,10 @@ >
-
+
{{ ability.name }}
@@ -108,8 +111,13 @@ :key="healthBar._id" >
-
- Total: {{ healthBar.total }} +
+ + Total: + + + {{ healthBar.total }} +
- + Total: - +
{{ resource.name }}
@@ -175,6 +183,7 @@
-
-
-
- Spell Slots -
-
-
- {{ spellSlot.name }} -
-
- Total: {{ spellSlot.total }} -
-
-
-
-
-
-
-
@@ -275,9 +249,7 @@
-
+
@@ -299,6 +271,41 @@
+
+
+
+ Spell Slots +
+
+
+ {{ spellSlot.name }} +
+
+ Total: {{ spellSlot.total }} +
+
+
+
+
+
+
+
note._id); + const topLevelNotes = getProperties(this.creature, { + type: 'note', + summary: { $exists: true }, + 'ancestor.id': {$nin: allNoteIds} + }); + return topLevelNotes; }, }, methods: { @@ -554,7 +569,6 @@ export default { min-width: 86px; text-align: center; margin: 4px 4px -10px; - padding: 8px; z-index: 1; } .ability .bottom { 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 9382ae14..4a63daa5 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue @@ -84,7 +84,7 @@ diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue index 4dbf8d14..13ec0b26 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue @@ -1,5 +1,8 @@