diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c795b054 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/app/imports/api/engine/actions/applyProperty.js b/app/imports/api/engine/actions/applyProperty.js index ec9c28d9..805d420e 100644 --- a/app/imports/api/engine/actions/applyProperty.js +++ b/app/imports/api/engine/actions/applyProperty.js @@ -2,6 +2,7 @@ import action from './applyPropertyByType/applyAction.js'; import adjustment from './applyPropertyByType/applyAdjustment.js'; import branch from './applyPropertyByType/applyBranch.js'; import buff from './applyPropertyByType/applyBuff.js'; +import buffRemover from './applyPropertyByType/applyBuffRemover.js'; import damage from './applyPropertyByType/applyDamage.js'; import note from './applyPropertyByType/applyNote.js'; import roll from './applyPropertyByType/applyRoll.js'; @@ -13,6 +14,7 @@ const applyPropertyByType = { adjustment, branch, buff, + buffRemover, damage, note, roll, diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index f666bad9..26da462a 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -11,8 +11,8 @@ import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js' export default function applyAction(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; - let targets = actionContext.targets; - if (prop.target === 'self') targets = [actionContext.creature]; + if (prop.target === 'self') actionContext.targets = [actionContext.creature]; + const targets = actionContext.targets; // Log the name and summary let content = { name: prop.name }; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js index b6e7d682..965ef8b3 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js @@ -21,10 +21,14 @@ export default function applyBuff(node, actionContext){ // Then copy the decendants of the buff to the targets let propList = [prop]; - function addChildrenToPropList(children){ + function addChildrenToPropList(children, { skipCrystalize } = {}){ children.forEach(child => { + if (skipCrystalize) child.node._skipCrystalize = true; propList.push(child.node); - addChildrenToPropList(child.children); + // recursively add the child's children, but don't crystalize nested buffs + addChildrenToPropList(child.children, { + skipCrystalize: skipCrystalize || child.node.type === 'buff' + }); }); } addChildrenToPropList(node.children); @@ -88,6 +92,10 @@ function copyNodeListToTarget(propList, target, oldParent){ */ function crystalizeVariables({propList, actionContext}){ propList.forEach(prop => { + if (prop._skipCrystalize) { + delete prop._skipCrystalize; + return; + } computedSchemas[prop.type].computedFields().forEach( calcKey => { applyFnToKey(prop, calcKey, (prop, key) => { const calcObj = get(prop, key); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js new file mode 100644 index 00000000..09e4e987 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js @@ -0,0 +1,101 @@ +import { findLast, difference, intersection, filter } from 'lodash'; +import applyProperty from '../applyProperty.js'; +import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import { getProperyAncestors, getPropertiesOfType } from '/imports/api/engine/loadCreatures.js'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import { softRemove } from '/imports/api/parenting/softRemove.js'; +import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js'; + +export default function applyBuffRemover(node, actionContext) { + // Apply triggers + applyNodeTriggers(node, 'before', actionContext); + + const prop = node.node; + + // Log Name + if (prop.name){ + actionContext.addLog({ name: prop.name }); + } + + // Remove buffs + if (prop.targetParentBuff) { + // Remove nearest ancestor buff + const ancestors = getProperyAncestors(actionContext.creature._id, prop._id); + const nearestBuff = findLast(ancestors, ancestor => ancestor.type === 'buff'); + if (!nearestBuff) { + actionContext.addLog({ + name: 'Error', + value: 'Buff remover does not have a parent buff to remove', + }); + return; + } + removeBuff(nearestBuff, actionContext); + } else { + // Get all the buffs targeted by tags + const allBuffs = getPropertiesOfType(actionContext.creature._id, 'buff'); + const targetedBuffs = filter(allBuffs, buff => { + if (buff.inactive) return false; + if (buffRemoverMatchTags(prop, buff)) return true; + }); + // Remove the buffs + if (prop.removeAll) { + // Remove all matching buffs + targetedBuffs.forEach(buff => { + removeBuff(buff, actionContext); + }); + } else { + // Sort in reverse order + targetedBuffs.sort((a, b) => b.order - a.order); + // Remove the one with the highest order + const buff = targetedBuffs[0]; + if (buff) { + removeBuff(buff, actionContext); + } + } + } + + // Apply triggers + applyNodeTriggers(node, 'after', actionContext); + // Apply children + node.children.forEach(child => applyProperty(child, actionContext)); +} + +function removeBuff(buff, actionContext) { + actionContext.addLog({ + name: 'Removed', + value: `${buff.name || 'Buff'}` + }); + softRemove({ _id: buff._id, collection: CreatureProperties }); +} + +function buffRemoverMatchTags(buffRemover, prop) { + let matched = false; + const propTags = getEffectivePropTags(prop); + // Check the target tags + if ( + !buffRemover.targetTags?.length || + difference(buffRemover.targetTags, propTags).length === 0 + ) { + matched = true; + } + // Check the extra tags + buffRemover.extraTags?.forEach(extra => { + if (extra.operation === 'OR') { + if (matched) return; + if ( + !extra.tags.length || + difference(extra.tags, propTags).length === 0 + ) { + matched = true; + } + } else if (extra.operation === 'NOT') { + if ( + extra.tags.length && + intersection(extra.tags, propTags) + ) { + return false; + } + } + }); + return matched; +} diff --git a/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js b/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js index 13549e73..315d93f0 100644 --- a/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js @@ -10,8 +10,10 @@ export default function computeToggleDependencies(node, dependencyGraph){ prop.enabled ) return; walkDown(node.children, child => { - child.node._computationDetails.toggleAncestors.push(prop); + // Only for children that aren't inactive + if (child.node.inactive) return; // The child nodes depend on the toggle condition compuation + child.node._computationDetails.toggleAncestors.push(prop); dependencyGraph.addLink(child.node._id, prop._id, 'toggle'); }); } diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index ad566b58..c729bf81 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -105,7 +105,8 @@ function linkBuff(dependencyGraph, prop){ dependOnCalc({dependencyGraph, prop, key: 'duration'}); } -function linkClassLevel(dependencyGraph, prop){ +function linkClassLevel(dependencyGraph, prop) { + if (prop.inactive) return; // The variableName of the prop depends on the prop if (prop.variableName && prop.level){ dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel'); @@ -121,11 +122,16 @@ function linkDamage(dependencyGraph, prop){ dependOnCalc({dependencyGraph, prop, key: 'amount'}); } -function linkEffects(dependencyGraph, prop, computation){ +function linkEffects(dependencyGraph, prop, computation) { // The effect depends on its amount calculation - dependOnCalc({dependencyGraph, prop, key: 'amount'}); + dependOnCalc({ dependencyGraph, prop, key: 'amount' }); + // Inactive effects aren't going to impact their targeted stats + if (prop.inactive) return; // The stats depend on the effect - if (prop.targetByTags){ + if (prop.inactive) { + // Inactive effects apply to no stats + return; + } else if (prop.targetByTags){ getEffectTagTargets(prop, computation).forEach(targetId => { const targetProp = computation.propsById[targetId]; if ( @@ -221,13 +227,14 @@ function linkRoll(dependencyGraph, prop){ } function linkVariableName(dependencyGraph, prop){ - // The variableName of the prop depends on the prop - if (prop.variableName){ + // The variableName of the prop depends on the prop if the prop is active + if (prop.variableName && !prop.inactive){ dependencyGraph.addLink(prop.variableName, prop._id, 'definition'); } } -function linkDamageMultiplier(dependencyGraph, prop){ +function linkDamageMultiplier(dependencyGraph, prop) { + if (prop.inactive) return; prop.damageTypes.forEach(damageType => { // Remove all non-letter characters from the damage name const damageName = damageType.replace(/[^a-z]/gi, '') @@ -237,6 +244,7 @@ function linkDamageMultiplier(dependencyGraph, prop){ function linkProficiencies(dependencyGraph, prop){ // The stats depend on the proficiency + if (prop.inactive) return; prop.stats.forEach(statName => { if (!statName) return; dependencyGraph.addLink(statName, prop._id, prop.type); @@ -248,6 +256,10 @@ function linkSavingThrow(dependencyGraph, prop){ } function linkSkill(dependencyGraph, prop){ + // Depends on base value + dependOnCalc({ dependencyGraph, prop, key: 'baseValue' }); + // Link dependents + if (prop.inactive) return; linkVariableName(dependencyGraph, prop); // The prop depends on the variable references as the ability if (prop.ability){ @@ -255,9 +267,6 @@ function linkSkill(dependencyGraph, prop){ } // Skills depend on the creature's proficiencyBonus dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus'); - - // Depends on base value - dependOnCalc({dependencyGraph, prop, key: 'baseValue'}); } function linkSlot(dependencyGraph, prop){ diff --git a/app/imports/api/engine/computation/buildCreatureComputation.js b/app/imports/api/engine/computation/buildCreatureComputation.js index d99d9290..a5009d2c 100644 --- a/app/imports/api/engine/computation/buildCreatureComputation.js +++ b/app/imports/api/engine/computation/buildCreatureComputation.js @@ -89,6 +89,10 @@ export function buildComputationFromProps(properties, creature, variables){ // Walk the property trees computing things that need to be inherited walkDown(forest, node => { computeInactiveStatus(node); + }); + // Inactive status must be complete for the whole tree before toggle deps + // are calculated + walkDown(forest, node => { computeToggleDependencies(node, dependencyGraph); computeSlotQuantityFilled(node, dependencyGraph); }); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/getAggregatorResult.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/getAggregatorResult.js index 3201009b..3a9f7316 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/getAggregatorResult.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/getAggregatorResult.js @@ -29,7 +29,7 @@ export default function getAggregatorResult(node){ if (aggregator.set !== undefined) { result = aggregator.set; } - if (!node.definingProp?.decimal && Number.isFinite(result)){ + if (!node.data.definingProp?.decimal && Number.isFinite(result)){ result = Math.floor(result); } else if (Number.isFinite(result)){ result = stripFloatingPointOddities(result); diff --git a/app/imports/api/engine/computation/computeCreatureComputation.js b/app/imports/api/engine/computation/computeCreatureComputation.js index 88dde8c7..7cd4b41c 100644 --- a/app/imports/api/engine/computation/computeCreatureComputation.js +++ b/app/imports/api/engine/computation/computeCreatureComputation.js @@ -51,11 +51,22 @@ function compute(computation, node){ function pushDependenciesToStack(nodeId, graph, stack, computation){ graph.forEachLinkedNode(nodeId, linkedNode => { - if (linkedNode._visitedChildren && !linkedNode._visited){ - const pather = path.nba(graph, { - oriented: true - }); - const loop = pather.find(nodeId, nodeId); + if (linkedNode._visitedChildren && !linkedNode._visited) { + // This is a dependency loop, find a path from the node to itself + // and store that path as a dependency loop error + const pather = path.nba(graph, { oriented: true }); + let loop = []; + // Pather doesn't like going from a node to iteself, so find all the + // paths going from the next node back to the original node + // and return the shortest one + graph.forEachLinkedNode(nodeId, nextNode => { + const newLoop = pather.find(nextNode.id, nodeId); + if (!newLoop.length) return; + if (!loop.length || newLoop.length < loop.length - 1) { + loop = [linkedNode, ...newLoop]; + } + }, true); + if (loop.length) { computation.errors.push({ type: 'dependencyLoop', diff --git a/app/imports/api/engine/loadCreatures.js b/app/imports/api/engine/loadCreatures.js index 9823f892..aa036a37 100644 --- a/app/imports/api/engine/loadCreatures.js +++ b/app/imports/api/engine/loadCreatures.js @@ -46,7 +46,6 @@ export function getSingleProperty(creatureId, propertyId) { 'removed': {$ne: true}, }, { sort: { order: 1 }, - fields: { icon: 0 }, }); // console.timeEnd(`Cache miss on creature properties: ${creatureId}`); return prop; @@ -65,7 +64,6 @@ export function getProperties(creatureId) { 'removed': {$ne: true}, }, { sort: { order: 1 }, - fields: { icon: 0 }, }).fetch(); // console.timeEnd(`Cache miss on creature properties: ${creatureId}`); return props; @@ -90,7 +88,6 @@ export function getPropertiesOfType(creatureId, propType) { 'type': propType, }, { sort: { order: 1 }, - fields: { icon: 0 }, }).fetch(); // console.timeEnd(`Cache miss on creature properties: ${creatureId}`); return props; @@ -100,14 +97,13 @@ export function getCreature(creatureId) { if (loadedCreatures.has(creatureId)) { const loadedCreature = loadedCreatures.get(creatureId); const creature = loadedCreature.creature; - if (creature) return creature; + if (creature) { + const cloneCreature = EJSON.clone(creature); + return cloneCreature; + } } // console.time(`Cache miss on Creature: ${creatureId}`); - const creature = Creatures.findOne(creatureId, { - denormalizedStats: 1, - variables: 1, - dirty: 1, - }); + const creature = Creatures.findOne(creatureId); // console.timeEnd(`Cache miss on Creature: ${creatureId}`); return creature; } @@ -116,7 +112,10 @@ export function getVariables(creatureId) { if (loadedCreatures.has(creatureId)) { const loadedCreature = loadedCreatures.get(creatureId); const variables = loadedCreature.variables; - if (variables) return variables; + if (variables) { + const cloneVarables = EJSON.clone(variables); + return cloneVarables; + } } // console.time(`Cache miss on variables: ${creatureId}`); const variables = CreatureVariables.findOne({_creatureId: creatureId}); @@ -149,6 +148,7 @@ export function getProperyAncestors(creatureId, propertyId) { // Fetch from database return CreatureProperties.find({ _id: { $in: ancestorIds }, + removed: {$ne: true}, }, { sort: { order: 1 }, }).fetch(); @@ -175,6 +175,8 @@ export function getPropertyDecendants(creatureId, propertyId) { return CreatureProperties.find({ 'ancestors.id': propertyId, removed: { $ne: true }, + }, { + sort: { order: 1 }, }).fetch(); } } @@ -199,7 +201,6 @@ class LoadedCreature { removed: { $ne: true }, }, { sort: { order: 1 }, - fields: { icon: 0 }, }).observeChanges({ added(id, fields) { fields._id = id; diff --git a/app/imports/api/properties/BuffRemovers.js b/app/imports/api/properties/BuffRemovers.js new file mode 100644 index 00000000..8752f750 --- /dev/null +++ b/app/imports/api/properties/BuffRemovers.js @@ -0,0 +1,79 @@ +import SimpleSchema from 'simpl-schema'; +import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; + +let BuffRemoverSchema = createPropertySchema({ + name: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + // This will remove just the nearest ancestor buff + targetParentBuff: { + type: Boolean, + optional: true, + }, + // The following only applies when not targeting the parent buff + // Which character to remove buffs from + target: { + type: String, + allowedValues: [ + 'self', + 'target', + ], + defaultValue: 'target', + }, + // remove 1 or remove all + removeAll: { + type: Boolean, + optional: true, + defaultValue: true, + }, + // Buffs to remove based on tags: + targetTags: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.tagCount, + }, + 'targetTags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, + extraTags: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.extraTagsCount, + }, + 'extraTags.$': { + type: Object, + }, + 'extraTags.$._id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + autoValue(){ + if (!this.isSet) return Random.id(); + } + }, + 'extraTags.$.operation': { + type: String, + allowedValues: ['OR', 'NOT'], + defaultValue: 'OR', + }, + 'extraTags.$.tags': { + type: Array, + defaultValue: [], + maxCount: STORAGE_LIMITS.tagCount, + }, + 'extraTags.$.tags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, +}); + +let ComputedOnlyBuffRemoverSchema = createPropertySchema({}); + +const ComputedBuffRemoverSchema = new SimpleSchema() + .extend(BuffRemoverSchema) + .extend(ComputedOnlyBuffRemoverSchema); + +export { BuffRemoverSchema, ComputedOnlyBuffRemoverSchema, ComputedBuffRemoverSchema }; diff --git a/app/imports/api/properties/Buffs.js b/app/imports/api/properties/Buffs.js index 43efd841..dac2d7ff 100644 --- a/app/imports/api/properties/Buffs.js +++ b/app/imports/api/properties/Buffs.js @@ -12,6 +12,10 @@ let BuffSchema = createPropertySchema({ type: 'inlineCalculationFieldToCompute', optional: true, }, + hideRemoveButton: { + type: Boolean, + optional: true, + }, // How many rounds this buff lasts duration: { type: 'fieldToCompute', diff --git a/app/imports/api/properties/Folders.js b/app/imports/api/properties/Folders.js index da4e5386..45055ce1 100644 --- a/app/imports/api/properties/Folders.js +++ b/app/imports/api/properties/Folders.js @@ -1,15 +1,15 @@ -import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; // Folders organize a character sheet into a tree, particularly to group things // like 'race' and 'background' -let FolderSchema = new SimpleSchema({ +let FolderSchema = new createPropertySchema({ name: { type: String, max: STORAGE_LIMITS.name, }, }); -const ComputedOnlyFolderSchema = new SimpleSchema({}); +const ComputedOnlyFolderSchema = new createPropertySchema({}); export { FolderSchema, ComputedOnlyFolderSchema }; diff --git a/app/imports/api/properties/Triggers.js b/app/imports/api/properties/Triggers.js index f8f6c4a3..4b66e422 100644 --- a/app/imports/api/properties/Triggers.js +++ b/app/imports/api/properties/Triggers.js @@ -25,6 +25,7 @@ const actionPropertyTypeOptions = { adjustment: 'Attribute damage', branch: 'Branch', buff: 'Buff', + buffRemover: 'Buff Removed', damage: 'Damage', note: 'Note', roll: 'Roll', diff --git a/app/imports/api/properties/computedOnlyPropertySchemasIndex.js b/app/imports/api/properties/computedOnlyPropertySchemasIndex.js index 489f5b19..93f3793a 100644 --- a/app/imports/api/properties/computedOnlyPropertySchemasIndex.js +++ b/app/imports/api/properties/computedOnlyPropertySchemasIndex.js @@ -3,6 +3,7 @@ import { ComputedOnlyActionSchema } from '/imports/api/properties/Actions.js'; import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustments.js'; import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js'; import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js'; +import { ComputedOnlyBuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js'; import { ComputedOnlyBranchSchema } from '/imports/api/properties/Branches.js'; import { ComputedOnlyClassSchema } from '/imports/api/properties/Classes.js'; import { ComputedOnlyClassLevelSchema } from '/imports/api/properties/ClassLevels.js'; @@ -33,6 +34,7 @@ const propertySchemasIndex = { adjustment: ComputedOnlyAdjustmentSchema, attribute: ComputedOnlyAttributeSchema, buff: ComputedOnlyBuffSchema, + buffRemover: ComputedOnlyBuffRemoverSchema, branch: ComputedOnlyBranchSchema, class: ComputedOnlyClassSchema, classLevel: ComputedOnlyClassLevelSchema, diff --git a/app/imports/api/properties/computedPropertySchemasIndex.js b/app/imports/api/properties/computedPropertySchemasIndex.js index 01741b49..7bdfb302 100644 --- a/app/imports/api/properties/computedPropertySchemasIndex.js +++ b/app/imports/api/properties/computedPropertySchemasIndex.js @@ -3,6 +3,7 @@ import { ComputedActionSchema } from '/imports/api/properties/Actions.js'; import { ComputedAdjustmentSchema } from '/imports/api/properties/Adjustments.js'; import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js'; import { ComputedBuffSchema } from '/imports/api/properties/Buffs.js'; +import { ComputedBuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js'; import { ComputedBranchSchema } from '/imports/api/properties/Branches.js'; import { ComputedClassSchema } from '/imports/api/properties/Classes.js'; import { ComputedClassLevelSchema } from '/imports/api/properties/ClassLevels.js'; @@ -33,6 +34,7 @@ const propertySchemasIndex = { adjustment: ComputedAdjustmentSchema, attribute: ComputedAttributeSchema, buff: ComputedBuffSchema, + buffRemover: ComputedBuffRemoverSchema, branch: ComputedBranchSchema, class: ComputedClassSchema, classLevel: ComputedClassLevelSchema, diff --git a/app/imports/api/properties/propertySchemasIndex.js b/app/imports/api/properties/propertySchemasIndex.js index 0f2f8cb0..fdccedc9 100644 --- a/app/imports/api/properties/propertySchemasIndex.js +++ b/app/imports/api/properties/propertySchemasIndex.js @@ -3,6 +3,7 @@ import { ActionSchema } from '/imports/api/properties/Actions.js'; import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js'; import { AttributeSchema } from '/imports/api/properties/Attributes.js'; import { BuffSchema } from '/imports/api/properties/Buffs.js'; +import { BuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js'; import { BranchSchema } from '/imports/api/properties/Branches.js'; import { ClassSchema } from '/imports/api/properties/Classes.js'; import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js'; @@ -33,6 +34,7 @@ const propertySchemasIndex = { adjustment: AdjustmentSchema, attribute: AttributeSchema, buff: BuffSchema, + buffRemover: BuffRemoverSchema, branch: BranchSchema, class: ClassSchema, classLevel: ClassLevelSchema, diff --git a/app/imports/constants/PROPERTIES.js b/app/imports/constants/PROPERTIES.js index 3d75bd06..30b3248c 100644 --- a/app/imports/constants/PROPERTIES.js +++ b/app/imports/constants/PROPERTIES.js @@ -24,6 +24,12 @@ const PROPERTIES = Object.freeze({ helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.', suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'], }, + buffRemover: { + icon: '$vuetify.icons.buffRemover', + name: 'Remove Buff', + helpText: 'Removes a buff from the target character', + suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'], + }, branch: { icon: 'mdi-file-tree', name: 'Branch', diff --git a/app/imports/constants/SVG_ICONS.js b/app/imports/constants/SVG_ICONS.js index 00ab7353..0ec43912 100644 --- a/app/imports/constants/SVG_ICONS.js +++ b/app/imports/constants/SVG_ICONS.js @@ -59,6 +59,10 @@ const SVG_ICONS = Object.freeze({ name: 'buff', shape: 'M331.924 20.385c-36.708.887-82.53 60.972-116.063 147.972h.003c30.564-65.57 71.17-106.39 97.348-99.378 28.058 7.516 37.11 69.42 24.847 148.405-.895-.32-1.773-.642-2.672-.96.893.367 1.765.738 2.65 1.106-2.988 19.215-7.22 39.424-12.767 60.12-2.77 10.332-5.763 20.39-8.936 30.14-24.996-3.82-52.374-9.537-80.82-17.16-105.856-28.36-186.115-72.12-179.307-97.53 4.257-15.884 42.167-23.775 95.908-20.29-74.427-8.7-128.912-2.044-135.035 20.803-9.038 33.73 89.168 89.372 219.147 124.2 24.436 6.55 48.267 11.897 70.918 16.042-28.965 75.878-68.293 126.078-96.653 118.48-21.817-5.85-35.995-45.443-36.316-100.206-4.79 75.476 9.278 131.945 40.66 140.356 38.836 10.407 91.394-54.998 127.896-152.98 80.12 10.74 138.958 4.278 145.38-19.682 6.384-23.82-41.025-58.44-115.102-89.03 20.713-109.022 8.483-198.5-31.96-209.34-2.968-.796-6.013-1.144-9.124-1.07zm40.568 213.086c44.65 22.992 71.146 47.135 67.07 62.348-4.055 15.13-38.104 20.457-87.333 16.303 3.415-10.604 6.64-21.502 9.63-32.663 4.176-15.588 7.713-30.965 10.632-45.986z', }, + 'perpendicular-rings-crossed-out': { + name: 'buffRemover', + shape: 'm 342.85706,29.520771 -10e-4,0.0017 C 311.09371,29.014264 281.87055,64.906082 247.60595,132.05856 L 172.54472,18.060082 142.22614,39.094548 433.13139,479.99983 459.83281,455.56605 385.30374,345.07055 c 60.47709,6.76324 95.40448,9.77027 101.5894,-10.08043 7.33528,-23.5444 -38.64515,-60.03954 -111.4339,-93.57921 25.07382,-108.10207 16.44748,-197.999126 -23.52751,-210.454283 -2.93364,-0.914533 -5.96321,-1.384872 -9.07467,-1.435856 z M 318.01727,76.44599 c 1.43939,0.165657 2.83703,0.458385 4.19071,0.879905 27.7335,8.636553 34.29121,70.855545 18.86666,149.284215 -0.88143,-0.35568 -1.74564,-0.7136 -2.63114,-1.06745 0.87755,0.40257 1.73435,0.80776 2.60387,1.21101 -3.29527,16.73399 -2.43292,17.48734 -7.73765,35.32955 L 249.50725,134.94386 C 277.16641,91.939481 296.57992,73.978789 318.01727,76.44599 Z M 21.309482,189.96942 c -10.385032,33.3398 85.507398,92.87964 213.982728,132.89854 18.67653,5.81939 37.00521,10.88752 54.75098,15.23025 -7.08963,-10.99585 -14.12857,-21.92647 -21.25144,-32.93262 -2.34002,-0.62085 -4.72317,-1.30046 -7.07998,-1.94408 -8.56911,-2.33994 -17.21776,-4.79347 -26.04044,-7.54193 C 131.03978,263.09208 52.602729,216.14328 60.425516,191.02722 65.316857,175.32699 103.51348,168.9655 157.07104,174.60556 121.54273,171.56302 37.239273,147.38028 21.309482,189.96942 Z M 374.83456,244.0657 c 43.69065,24.76623 69.19628,49.95346 64.51274,64.99048 -4.48269,14.38828 -26.92387,13.08335 -72.99123,7.92393 L 362.747,311.5007 c -5.21026,-7.91039 -3.28718,-12.58648 -0.38327,-21.91074 4.79853,-15.40771 8.94901,-30.63076 12.46879,-45.52239 z m -74.68362,109.69514 c -31.13564,67.76298 -69.48034,110.7402 -95.97392,102.48874 -21.56445,-6.72129 -34.14164,-46.85287 -32.26347,-101.58445 -7.81672,75.22256 3.97274,132.21064 34.99162,141.87493 32.83746,10.2293 77.9122,-34.67 115.55909,-108.20499 -7.40453,-11.47654 -14.88401,-23.0444 -22.31332,-34.57423 z' + }, 'roll': { name: 'roll', shape: 'M 339.33314,69.985523 152.23146,95.161367 297.9199,159.07076 Z m 13.13639,6.106743 -41.41324,89.085234 142.77019,70.18694 z M 286.72755,169.97878 132.07338,102.13811 116.91912,287.72473 Z m 23.20215,10.78603 19.43763,205.72115 132.11738,-131.21375 z m -14.47505,0.7893 -172.49845,119.61061 192.24446,89.36907 z M 115.16567,131.2299 49.03503,273.48548 l 52.96523,18.92787 z m 334.97786,155.72184 -114.74252,113.9631 48.61189,28.29247 z m -329.82146,28.96669 45.63657,144.20053 139.66241,-58.06123 z m -17.69094,-7.89455 -46.061724,-16.46047 81.264174,127.69506 z m 220.42858,102.47108 -107.73294,44.78793 150.00722,-20.18447 z', diff --git a/app/imports/server/publications/slotFillers.js b/app/imports/server/publications/slotFillers.js index 30dadc9f..6427af79 100644 --- a/app/imports/server/publications/slotFillers.js +++ b/app/imports/server/publications/slotFillers.js @@ -100,21 +100,18 @@ Meteor.publish('classFillers', function(classId){ } // Get all the ids of libraries the user can access - const user = Meteor.users.findOne(userId, { - fields: {subscribedLibraries: 1} - }); - const subs = user && user.subscribedLibraries || []; - let libraries = Libraries.find({ + const creatureId = classProp.ancestors[0].id; + const libraryIds = getCreatureLibraryIds(creatureId, userId); + const libraries = Libraries.find({ $or: [ - {owner: userId}, - {writers: userId}, - {readers: userId}, - {_id: {$in: subs}}, + { owner: userId }, + { writers: userId }, + { readers: userId }, + { _id: { $in: libraryIds }, public: true }, ] }, { - fields: {_id: 1, name: 1}, + sort: { name: 1 } }); - let libraryIds = libraries.map(lib => lib._id); // Build a filter for nodes in those libraries that match the slot let filter = getSlotFillFilter({slot: classProp, libraryIds}); diff --git a/app/imports/ui/components/ColumnLayout.vue b/app/imports/ui/components/ColumnLayout.vue index 42e1a9a3..de3e1448 100644 --- a/app/imports/ui/components/ColumnLayout.vue +++ b/app/imports/ui/components/ColumnLayout.vue @@ -18,7 +18,7 @@ export default { }; - diff --git a/app/imports/ui/properties/forms/shared/propertyFormIndex.js b/app/imports/ui/properties/forms/shared/propertyFormIndex.js index 63c4fe45..bd6c6ba9 100644 --- a/app/imports/ui/properties/forms/shared/propertyFormIndex.js +++ b/app/imports/ui/properties/forms/shared/propertyFormIndex.js @@ -2,6 +2,7 @@ const ActionForm = () => import('/imports/ui/properties/forms/ActionForm.vue'); const AdjustmentForm = () => import('/imports/ui/properties/forms/AdjustmentForm.vue'); const AttributeForm = () => import('/imports/ui/properties/forms/AttributeForm.vue'); const BuffForm = () => import('/imports/ui/properties/forms/BuffForm.vue'); +const BuffRemoverForm = () => import('/imports/ui/properties/forms/BuffRemoverForm.vue'); const BranchForm = () => import('/imports/ui/properties/forms/BranchForm.vue'); const ClassForm = () => import('/imports/ui/properties/forms/ClassForm.vue'); const ClassLevelForm = () => import('/imports/ui/properties/forms/ClassLevelForm.vue'); @@ -32,6 +33,7 @@ export default { adjustment: AdjustmentForm, attribute: AttributeForm, buff: BuffForm, + buffRemover: BuffRemoverForm, branch: BranchForm, constant: ConstantForm, container: ContainerForm, diff --git a/app/imports/ui/properties/viewers/BuffRemoverViewer.vue b/app/imports/ui/properties/viewers/BuffRemoverViewer.vue new file mode 100644 index 00000000..89097b7e --- /dev/null +++ b/app/imports/ui/properties/viewers/BuffRemoverViewer.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js b/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js index 572bfad0..cef19b01 100644 --- a/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js +++ b/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js @@ -2,6 +2,7 @@ const ActionViewer = () => import ('/imports/ui/properties/viewers/ActionViewer. const AdjustmentViewer = () => import ('/imports/ui/properties/viewers/AdjustmentViewer.vue'); const AttributeViewer = () => import ('/imports/ui/properties/viewers/AttributeViewer.vue'); const BuffViewer = () => import ('/imports/ui/properties/viewers/BuffViewer.vue'); +const BuffRemoverViewer = () => import ('/imports/ui/properties/viewers/BuffRemoverViewer.vue'); const BranchViewer = () => import ('/imports/ui/properties/viewers/BranchViewer.vue'); const ContainerViewer = () => import ('/imports/ui/properties/viewers/ContainerViewer.vue'); const ClassViewer = () => import ('/imports/ui/properties/viewers/ClassViewer.vue'); @@ -31,6 +32,7 @@ export default { adjustment: AdjustmentViewer, attribute: AttributeViewer, buff: BuffViewer, + buffRemover: BuffRemoverViewer, branch: BranchViewer, container: ContainerViewer, class: ClassViewer, diff --git a/app/imports/ui/vuexStore.js b/app/imports/ui/vuexStore.js index e06c5f61..173e276b 100644 --- a/app/imports/ui/vuexStore.js +++ b/app/imports/ui/vuexStore.js @@ -1,6 +1,9 @@ import Vue from 'vue'; import Vuex from 'vuex'; import dialogStackStore from '/imports/ui/dialogStack/dialogStackStore.js'; +import Creatures from '/imports/api/creature/creatures/Creatures.js'; +const tabs = ['stats', 'features', 'inventory', 'spells', 'journal', 'build', 'tree']; +const tabsWithoutSpells = ['stats', 'features', 'inventory', 'journal', 'build', 'tree']; Vue.use(Vuex); const store = new Vuex.Store({ @@ -16,13 +19,21 @@ const store = new Vuex.Store({ showDetailsDialog: false, }, getters: { - // ... tabById: (state) => (id) => { if (id in state.characterSheetTabs){ return state.characterSheetTabs[id]; } else { return 0; } + }, + tabNameById: (state) => (id) => { + const tabNumber = state.characterSheetTabs[id]; + const creature = Creatures.findOne(id); + if (creature?.settings?.hideSpellsTab) { + return tabsWithoutSpells[tabNumber]; + } else { + return tabs[tabNumber] + } } }, mutations: { diff --git a/app/package.json b/app/package.json index bc90f96d..99bb0f84 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dicecloud", - "version": "2.0.33", + "version": "2.0.38", "description": "Unofficial Online Realtime D&D 5e App", "license": "GPL-3.0", "repository": { @@ -11,7 +11,8 @@ "scripts": { "run": "meteor", "debug": "meteor --inspect", - "test": "meteor test --driver-package meteortesting:mocha --port 3001" + "test": "meteor test --driver-package meteortesting:mocha --port 3001", + "build": "meteor build ../build --architecture os.linux.x86_64" }, "engines": { "node": "14.0.x",