diff --git a/app/imports/api/creature/creatureProperties/CreatureProperties.js b/app/imports/api/creature/creatureProperties/CreatureProperties.js
index c3957dfd..b451dd1d 100644
--- a/app/imports/api/creature/creatureProperties/CreatureProperties.js
+++ b/app/imports/api/creature/creatureProperties/CreatureProperties.js
@@ -88,6 +88,12 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
index: 1,
removeBeforeCompute: true,
},
+ deactivatingToggleId: {
+ type: String,
+ regEx: SimpleSchema.RegEx.Id,
+ optional: true,
+ removeBeforeCompute: true,
+ },
// When this is true on any property, the creature needs to be recomputed
dirty: {
type: Boolean,
diff --git a/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js b/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js
index b7a98595..3d9bf564 100644
--- a/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js
+++ b/app/imports/api/engine/computation/buildComputation/computeToggleDependencies.js
@@ -1,16 +1,31 @@
import walkDown from '/imports/api/engine/computation/utility/walkdown.js';
+import { getEffectTagTargets } from '/imports/api/engine/computation/buildComputation/linkTypeDependencies.js';
-export default function computeToggleDependencies(node, dependencyGraph){
+export default function computeToggleDependencies(node, dependencyGraph, computation, forest) {
const prop = node.node;
- // Only for toggles that aren't inactive and aren't set to enabled or disabled
- if (
- prop.inactive ||
- prop.type !== 'toggle' ||
- prop.disabled ||
- prop.enabled
- ) return;
+ // Only for toggles
+ if (prop.type !== 'toggle') return;
+
+ if (prop.targetByTags) {
+ // Find all the props targeted by tags, and disable them and their children
+ getEffectTagTargets(prop, computation).forEach(targetId => {
+ const target = forest.nodeIndex[targetId];
+ if (!target) return;
+ target.node._computationDetails.toggleAncestors.push(prop);
+ dependencyGraph.addLink(target.node._id, prop._id, 'toggle');
+ walkDown(target.children, child => {
+ // The child nodes depend on the toggle
+ child.node._computationDetails.toggleAncestors.push(prop);
+ dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
+ });
+ });
+ }
+
+ // We don't need to link direct children of static toggles, it's already done
+ if (prop.disabled || prop.enabled) return;
+
walkDown(node.children, child => {
- // The child nodes depend on the toggle condition compuation
+ // The child nodes depend on the toggle
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 4913dc7d..826eaaee 100644
--- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js
+++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js
@@ -164,7 +164,7 @@ function linkEffects(dependencyGraph, prop, computation) {
}
// Returns an array of IDs of the properties the effect targets
-function getEffectTagTargets(effect, computation) {
+export function getEffectTagTargets(effect, computation) {
let targets = getTargetListFromTags(effect.targetTags, computation);
let notIds = [];
if (effect.extraTags) {
diff --git a/app/imports/api/engine/computation/buildCreatureComputation.js b/app/imports/api/engine/computation/buildCreatureComputation.js
index a736354c..a97f5b0a 100644
--- a/app/imports/api/engine/computation/buildCreatureComputation.js
+++ b/app/imports/api/engine/computation/buildCreatureComputation.js
@@ -29,7 +29,7 @@ import removeSchemaFields from './buildComputation/removeSchemaFields.js';
* computed toggles
*/
-export default function buildCreatureComputation(creatureId){
+export default function buildCreatureComputation(creatureId) {
const creature = getCreature(creatureId);
const variables = getVariables(creatureId);
const properties = getProperties(creatureId);
@@ -37,7 +37,7 @@ export default function buildCreatureComputation(creatureId){
return computation;
}
-export function buildComputationFromProps(properties, creature, variables){
+export function buildComputationFromProps(properties, creature, variables) {
const computation = new CreatureComputation(properties, creature, variables);
// Dependency graph where edge(a, b) means a depends on b
@@ -49,14 +49,14 @@ export function buildComputationFromProps(properties, creature, variables){
const dependencyGraph = computation.dependencyGraph;
// Link the denormalizedStats from the creature
- if (creature && creature.denormalizedStats){
- if (creature.denormalizedStats.xp){
+ if (creature && creature.denormalizedStats) {
+ if (creature.denormalizedStats.xp) {
dependencyGraph.addNode('xp', {
baseValue: creature.denormalizedStats.xp,
type: '_variable'
});
}
- if (creature.denormalizedStats.milestoneLevels){
+ if (creature.denormalizedStats.milestoneLevels) {
dependencyGraph.addNode('milestoneLevels', {
baseValue: creature.denormalizedStats.milestoneLevels,
type: '_variable'
@@ -93,7 +93,7 @@ export function buildComputationFromProps(properties, creature, variables){
// Inactive status must be complete for the whole tree before toggle deps
// are calculated
walkDown(forest, node => {
- computeToggleDependencies(node, dependencyGraph);
+ computeToggleDependencies(node, dependencyGraph, computation, forest);
computeSlotQuantityFilled(node, dependencyGraph);
});
diff --git a/app/imports/api/engine/computation/computeComputation/computeByType.js b/app/imports/api/engine/computation/computeComputation/computeByType.js
index 0b5633e3..0f3d5436 100644
--- a/app/imports/api/engine/computation/computeComputation/computeByType.js
+++ b/app/imports/api/engine/computation/computeComputation/computeByType.js
@@ -6,6 +6,7 @@ import pointBuy from './computeByType/computePointBuy.js';
import propertySlot from './computeByType/computeSlot.js';
import container from './computeByType/computeContainer.js';
import spellList from './computeByType/computeSpellList.js';
+import toggle from './computeByType/computeToggle.js';
import _calculation from './computeByType/computeCalculation.js';
export default Object.freeze({
@@ -19,4 +20,5 @@ export default Object.freeze({
propertySlot,
spell: action,
spellList,
+ toggle,
});
diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeToggle.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeToggle.js
new file mode 100644
index 00000000..bba5c4ff
--- /dev/null
+++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeToggle.js
@@ -0,0 +1,7 @@
+export default function computeToggle(computation, node) {
+ const prop = node.data;
+ if (!prop.enabled && !prop.disabled && prop.condition && !prop.condition.value) {
+ prop.inactive = true;
+ prop.deactivatedBySelf = true;
+ }
+}
diff --git a/app/imports/api/engine/computation/computeComputation/computeToggles.js b/app/imports/api/engine/computation/computeComputation/computeToggles.js
index caa310d2..657305d6 100644
--- a/app/imports/api/engine/computation/computeComputation/computeToggles.js
+++ b/app/imports/api/engine/computation/computeComputation/computeToggles.js
@@ -1,13 +1,16 @@
-export default function evaluateToggles(computation, node){
+export default function evaluateToggles(computation, node) {
let prop = node.data;
if (!prop) return;
let toggles = prop._computationDetails?.toggleAncestors;
if (!toggles) return;
toggles.forEach(toggle => {
- if (!toggle.condition) return;
- if (!toggle.condition.value){
+ if (
+ (!toggle.enabled && !toggle.disabled && toggle.condition && !toggle.condition.value)
+ || (toggle.disabled)
+ ) {
prop.inactive = true;
prop.deactivatedByToggle = true;
+ prop.deactivatingToggleId = toggle._id;
}
});
}
diff --git a/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js b/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js
index fcad024c..0548fedd 100644
--- a/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js
+++ b/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js
@@ -3,12 +3,12 @@ import { EJSON } from 'meteor/ejson';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import propertySchemasIndex from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
-export default function writeAlteredProperties(computation){
+export default function writeAlteredProperties(computation) {
let bulkWriteOperations = [];
// Loop through all properties on the memo
computation.props.forEach(changed => {
let schema = propertySchemasIndex[changed.type];
- if (!schema){
+ if (!schema) {
console.warn('No schema for ' + changed.type);
return;
}
@@ -20,12 +20,13 @@ export default function writeAlteredProperties(computation){
'deactivatedBySelf',
'deactivatedByAncestor',
'deactivatedByToggle',
+ 'deactivatingToggleId',
'damage',
'dirty',
...schema.objectKeys(),
];
op = addChangedKeysToOp(op, keys, original, changed);
- if (op){
+ if (op) {
bulkWriteOperations.push(op);
}
});
@@ -37,10 +38,10 @@ function addChangedKeysToOp(op, keys, original, changed) {
// Loop through all keys that can be changed by computation
// and compile an operation that sets all those keys
for (let key of keys) {
- if (!EJSON.equals(original[key], changed[key])){
+ if (!EJSON.equals(original[key], changed[key])) {
if (!op) op = newOperation(original._id, changed.type);
let value = changed[key];
- if (value === undefined){
+ if (value === undefined) {
// Unset values that become undefined
addUnsetOp(op, key);
} else {
@@ -52,32 +53,32 @@ function addChangedKeysToOp(op, keys, original, changed) {
return op;
}
-function newOperation(_id, type){
+function newOperation(_id, type) {
let newOp = {
updateOne: {
- filter: {_id},
+ filter: { _id },
update: {},
}
};
- if (Meteor.isClient){
+ if (Meteor.isClient) {
newOp.type = type;
}
return newOp;
}
-function addSetOp(op, key, value){
- if (op.updateOne.update.$set){
+function addSetOp(op, key, value) {
+ if (op.updateOne.update.$set) {
op.updateOne.update.$set[key] = value;
} else {
- op.updateOne.update.$set = {[key]: value};
+ op.updateOne.update.$set = { [key]: value };
}
}
-function addUnsetOp(op, key){
- if (op.updateOne.update.$unset){
+function addUnsetOp(op, key) {
+ if (op.updateOne.update.$unset) {
op.updateOne.update.$unset[key] = 1;
} else {
- op.updateOne.update.$unset = {[key]: 1};
+ op.updateOne.update.$unset = { [key]: 1 };
}
}
@@ -100,14 +101,14 @@ function writePropertiesSequentially(bulkWriteOps) {
// in the UI because of incompatibility with latency compensation. If the
// duplicate redraws can be fixed, this is a strictly better way of processing
// writes
-function bulkWriteProperties(bulkWriteOps){
+function bulkWriteProperties(bulkWriteOps) {
if (!bulkWriteOps.length) return;
// bulkWrite is only available on the server
if (Meteor.isServer) {
CreatureProperties.rawCollection().bulkWrite(
bulkWriteOps,
- {ordered : false},
- function(e){
+ { ordered: false },
+ function (e) {
if (e) {
console.error('Bulk write failed: ');
console.error(e);
diff --git a/app/imports/api/parenting/nodesToTree.js b/app/imports/api/parenting/nodesToTree.js
index 3e4b5733..fae36df1 100644
--- a/app/imports/api/parenting/nodesToTree.js
+++ b/app/imports/api/parenting/nodesToTree.js
@@ -26,6 +26,7 @@ export function nodeArrayToTree(nodes) {
forest.push(treeNode);
}
});
+ forest.nodeIndex = nodeIndex;
return forest;
}
diff --git a/app/imports/api/properties/Effects.js b/app/imports/api/properties/Effects.js
index e1ba0c37..77eadbeb 100644
--- a/app/imports/api/properties/Effects.js
+++ b/app/imports/api/properties/Effects.js
@@ -1,6 +1,7 @@
import SimpleSchema from 'simpl-schema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
+import TagTargetingSchema from '/imports/api/properties/subSchemas/TagTargetingSchema.js';
/*
* Effects are reason-value attached to skills and abilities
@@ -50,57 +51,7 @@ let EffectSchema = createPropertySchema({
type: String,
max: STORAGE_LIMITS.variableName,
},
- // True when targeting by tags instead of stats
- targetByTags: {
- type: Boolean,
- optional: true,
- },
- // If targeting by tags, the field which will be targeted
- targetField: {
- type: String,
- optional: true,
- max: STORAGE_LIMITS.variableName,
- },
- // Which tags the effect is applied to
- 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,
- },
-});
+}).extend(TagTargetingSchema);
const ComputedOnlyEffectSchema = createPropertySchema({
amount: {
diff --git a/app/imports/api/properties/Proficiencies.js b/app/imports/api/properties/Proficiencies.js
index 151f5509..cec32217 100644
--- a/app/imports/api/properties/Proficiencies.js
+++ b/app/imports/api/properties/Proficiencies.js
@@ -1,5 +1,6 @@
import SimpleSchema from 'simpl-schema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
+import TagTargetingSchema from '/imports/api/properties/subSchemas/TagTargetingSchema.js';
let ProficiencySchema = new SimpleSchema({
name: {
@@ -24,57 +25,7 @@ let ProficiencySchema = new SimpleSchema({
allowedValues: [0.49, 0.5, 1, 2],
defaultValue: 1,
},
- // True when targeting by tags instead of stats
- targetByTags: {
- type: Boolean,
- optional: true,
- },
- // If targeting by tags, the field which will be targeted
- targetField: {
- type: String,
- optional: true,
- max: STORAGE_LIMITS.variableName,
- },
- // Which tags the proficiency is applied to
- 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,
- },
-});
+}).extend(TagTargetingSchema);
const ComputedOnlyProficiencySchema = new SimpleSchema({});
diff --git a/app/imports/api/properties/Skills.js b/app/imports/api/properties/Skills.js
index ecd9dd20..7f15df3a 100644
--- a/app/imports/api/properties/Skills.js
+++ b/app/imports/api/properties/Skills.js
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
+import TagTargetingSchema from '/imports/api/properties/subSchemas/TagTargetingSchema.js';
/*
* Skills are anything that results in a modifier to be added to a D20
@@ -59,52 +60,8 @@ let SkillSchema = createPropertySchema({
type: 'inlineCalculationFieldToCompute',
optional: true,
},
- // Skills can apply their value to other calculations as a proficiency
- // True when applying skill to tagged props
- targetByTags: {
- type: Boolean,
- optional: true,
- },
- // Which tags the proficiency is applied to
- 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,
- },
-});
+ // Skills can apply their value to other calculations as a proficiency using tag targeting
+}).extend(TagTargetingSchema);
let ComputedOnlySkillSchema = createPropertySchema({
// Computed value of skill to be added to skill rolls
diff --git a/app/imports/api/properties/Toggles.js b/app/imports/api/properties/Toggles.js
index 755ed89b..c5babdc6 100644
--- a/app/imports/api/properties/Toggles.js
+++ b/app/imports/api/properties/Toggles.js
@@ -1,6 +1,7 @@
import SimpleSchema from 'simpl-schema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
+import TagTargetingSchema from '/imports/api/properties/subSchemas/TagTargetingSchema.js';
const ToggleSchema = createPropertySchema({
name: {
@@ -31,7 +32,7 @@ const ToggleSchema = createPropertySchema({
type: 'fieldToCompute',
optional: true,
},
-});
+}).extend(TagTargetingSchema);
const ComputedOnlyToggleSchema = createPropertySchema({
condition: {
diff --git a/app/imports/api/properties/subSchemas/TagTargetingSchema.js b/app/imports/api/properties/subSchemas/TagTargetingSchema.js
new file mode 100644
index 00000000..8bbe1ddb
--- /dev/null
+++ b/app/imports/api/properties/subSchemas/TagTargetingSchema.js
@@ -0,0 +1,57 @@
+import SimpleSchema from 'simpl-schema';
+import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
+
+const TagTargetingSchema = new SimpleSchema({
+ // True when targeting by tags instead of stats
+ targetByTags: {
+ type: Boolean,
+ optional: true,
+ },
+ // If targeting by tags, the field which will be targeted
+ targetField: {
+ type: String,
+ optional: true,
+ max: STORAGE_LIMITS.variableName,
+ },
+ // Which tags the effect is applied to
+ 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,
+ },
+});
+
+export default TagTargetingSchema;
diff --git a/app/imports/client/ui/properties/forms/ToggleForm.vue b/app/imports/client/ui/properties/forms/ToggleForm.vue
index d288666a..24cbd6d5 100644
--- a/app/imports/client/ui/properties/forms/ToggleForm.vue
+++ b/app/imports/client/ui/properties/forms/ToggleForm.vue
@@ -45,6 +45,29 @@
/>
+
+
+
+
+
+ $emit('change', e)"
+ @push="e => $emit('push', e)"
+ @pull="e => $emit('pull', e)"
+ />
+
+
@@ -69,8 +92,12 @@
diff --git a/app/imports/client/ui/properties/viewers/shared/PropertyTargetTags.vue b/app/imports/client/ui/properties/viewers/shared/PropertyTargetTags.vue
new file mode 100644
index 00000000..d7b14d3b
--- /dev/null
+++ b/app/imports/client/ui/properties/viewers/shared/PropertyTargetTags.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+ {{ tag }}
+
+
+
+
+ {{ ex.operation }}
+
+
+
+ {{ extraTag }}
+
+
+
+
+
+
+
+
\ No newline at end of file