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 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ tag }}
+
+
+
+
+ {{ ex.operation }}
+
+
+
+ {{ extraTag }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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",