From 5c0a2a4d6c7ddaa4841ef9792e537c369453bc74 Mon Sep 17 00:00:00 2001 From: Thaum Rystra Date: Sat, 16 May 2020 22:03:21 +0200 Subject: [PATCH] Overhauled computations to allow for toggles :'( that sucked --- .../creature/computation/ComputationMemo.js | 140 +++++++++++++----- .../creature/computation/EffectAggregator.js | 3 + .../api/creature/computation/applyToggles.js | 11 ++ .../api/creature/computation/combineStat.js | 10 +- .../api/creature/computation/computeEffect.js | 19 +++ .../api/creature/computation/computeMemo.js | 11 +- .../api/creature/computation/computeStat.js | 24 ++- .../api/creature/computation/computeToggle.js | 30 ++++ .../creature/computation/recomputeCreature.js | 7 +- .../computation/writeAlteredProperties.js | 80 +++++----- .../recomputeDamageMultipliers.js | 5 +- .../api/creature/getActiveProperties.js | 15 +- app/imports/api/properties/Actions.js | 5 - app/imports/api/properties/Adjustments.js | 27 ++++ app/imports/api/properties/Buffs.js | 43 ++---- .../DamageSchema.js => Damages.js} | 12 +- app/imports/api/properties/Effects.js | 13 +- app/imports/api/properties/Results.js | 15 ++ app/imports/api/properties/Rolls.js | 8 - app/imports/api/properties/SavingThrows.js | 13 +- app/imports/api/properties/Toggles.js | 36 +++++ .../computedPropertySchemasIndex.js | 8 + .../api/properties/propertySchemasIndex.js | 8 + .../properties/subSchemas/ResultsSchema.js | 36 ----- app/imports/constants/PROPERTIES.js | 4 + .../character/characterSheetTabs/StatsTab.vue | 13 +- .../ui/properties/forms/ResultsForm.vue | 52 ++++--- app/imports/ui/properties/forms/RollForm.vue | 29 ---- .../ui/properties/forms/ToggleForm.vue | 40 +++++ .../forms/shared/propertyFormIndex.js | 2 + 30 files changed, 468 insertions(+), 251 deletions(-) create mode 100644 app/imports/api/creature/computation/applyToggles.js create mode 100644 app/imports/api/creature/computation/computeToggle.js create mode 100644 app/imports/api/properties/Adjustments.js rename app/imports/api/properties/{subSchemas/DamageSchema.js => Damages.js} (73%) create mode 100644 app/imports/api/properties/Results.js create mode 100644 app/imports/api/properties/Toggles.js delete mode 100644 app/imports/api/properties/subSchemas/ResultsSchema.js create mode 100644 app/imports/ui/properties/forms/ToggleForm.vue diff --git a/app/imports/api/creature/computation/ComputationMemo.js b/app/imports/api/creature/computation/ComputationMemo.js index 8739e1eb..d2f15ca0 100644 --- a/app/imports/api/creature/computation/ComputationMemo.js +++ b/app/imports/api/creature/computation/ComputationMemo.js @@ -1,22 +1,45 @@ -import { includes, cloneDeep, has } from 'lodash'; +import { includes, cloneDeep } from 'lodash'; +// The computation memo is an in-memory data structure used only during the +// computation process export default class ComputationMemo { constructor(props){ this.statsByVariableName = {}; + this.extraStatsByVariableName = {}; + this.statsById = {}; this.originalPropsById = {}; this.propsById = {}; this.skillsByAbility = {}; this.unassignedEffects = []; - this.classes = {}; - props.filter((prop) => { - // skip effects, proficiencies, and class levels for the next pass + this.classLevelsById = {}; + this.togglesById = {}; + this.toggleIds = new Set(); + // First note all the ids of all the toggles + props.forEach((prop) => { if ( - prop.type === 'effect' || - prop.type === 'proficiency' || - prop.type === 'classLevel' - ) return true; - // Add all the stats - this.addStat(prop); + prop.type === 'toggle' + ) { + this.toggleIds.add(prop._id); + } + }); + props.filter((prop) => { + if ( + prop.type === 'toggle' + ) { + this.addToggle(prop); + } else { + return true; + } + }).filter((prop) => { + if ( + prop.type === 'attribute' || + prop.type === 'skill' + ) { + // Add all the stats + this.addStat(prop); + } else { + return true; + } }).forEach((prop) => { // Now add all effects and proficiencies if (prop.type === 'effect'){ @@ -32,8 +55,14 @@ export default class ComputationMemo { this.originalPropsById[prop._id] = cloneDeep(prop); this.propsById[prop._id] = prop; prop.computationDetails = propDetails(prop); + prop.ancestors.forEach(ancestor => { + if (this.toggleIds.has(ancestor.id)){ + prop.computationDetails.toggleAncestors.push(ancestor.id); + } + }); return prop; } + /* storeHighestClassLevel(name, prop, isBaseClass){ // Only store the highest level classLevel let stat = this.statsByVariableName[name] @@ -72,33 +101,51 @@ export default class ComputationMemo { level += this.classes[name].level || 0; } this.statsByVariableName['level'].value = level; + }*/ + addToggle(prop){ + prop = this.registerProperty(prop); + this.togglesById[prop._id] = prop; } addClassLevel(prop){ prop = this.registerProperty(prop); - if (prop.variableName){ - this.storeHighestClassLevel(prop.variableName, prop); - } - if (prop.baseClass){ - this.storeHighestClassLevel(prop.baseClass, prop, true); - } + this.classLevelsById[prop._id] = prop; } addStat(prop){ - prop = this.registerProperty(prop); let variableName = prop.variableName; if (!variableName) return; - if (this.statsByVariableName[variableName]){ - prop.value = NaN; - prop.computationDetails.error = 'variableNameCollision'; - if (Meteor.isClient) console.warn('variableNameCollision', prop); - return; - } - this.statsByVariableName[variableName] = prop; - if ( - prop.type === 'skill' && - isSkillCheck(prop) && - prop.ability - ){ - this.addSkillToAbility(prop, prop.ability) + let existingStat = this.statsByVariableName[variableName]; + if (existingStat){ + existingStat.computationDetails.idsOfSameName.push(prop._id); + this.originalPropsById[prop._id] = cloneDeep(prop); + if (prop.baseValueCalculation){ + existingStat.computationDetails.effects.push({ + operation: 'base', + calculation: prop.baseValueCalculation, + stats: [variableName], + computationDetails: propDetailsByType.effect(), + statBase: true, + }); + } + if (prop.baseProficiency){ + existingStat.computationDetails.proficiencies.push({ + value: prop.baseProficiency, + stats: [variableName], + computationDetails: propDetailsByType.proficiency(), + type: 'proficiency', + statBase: true, + }); + } + } else { + prop = this.registerProperty(prop); + this.statsById[prop._id] = prop; + this.statsByVariableName[variableName] = prop; + if ( + prop.type === 'skill' && + isSkillCheck(prop) && + prop.ability + ){ + this.addSkillToAbility(prop, prop.ability) + } } } addSkillToAbility(prop, ability){ @@ -152,9 +199,14 @@ export default class ComputationMemo { let target = this.statsByVariableName[statName]; if (!target) return; targets.add(target); - if (isAbility(target) && isSkillCheck(prop)) { + if (isAbility(target)) { let extras = this.skillsByAbility[statName] || []; - targets.add(...extras) + extras.forEach(ex =>{ + // Only pass on ability proficiencies to skills and checks + if (ex.skillType === 'skill' || ex.skillType === 'check'){ + targets.add(ex) + } + }); } }); return targets; @@ -188,11 +240,22 @@ function propDetails(prop){ } const propDetailsByType = { + toggle(){ + return { + computed: false, + busyComputing: false, + toggleAncestors: [], + disabledByToggle: false, + }; + }, attribute(){ return { computed: false, busyComputing: false, effects: [], + toggleAncestors: [], + disabledByToggle: false, + idsOfSameName: [], }; }, skill(){ @@ -201,19 +264,30 @@ const propDetailsByType = { busyComputing: false, effects: [], proficiencies: [], + toggleAncestors: [], + disabledByToggle: false, + idsOfSameName: [], }; }, effect(){ return { computed: false, + busyComputing: false, + toggleAncestors: [], + disabledByToggle: false, }; }, classLevel(){ return { computed: true, + toggleAncestors: [], + disabledByToggle: false, }; }, proficiency(){ - return {}; + return { + toggleAncestors: [], + disabledByToggle: false, + }; }, } diff --git a/app/imports/api/creature/computation/EffectAggregator.js b/app/imports/api/creature/computation/EffectAggregator.js index 32d53af1..a4b2fd16 100644 --- a/app/imports/api/creature/computation/EffectAggregator.js +++ b/app/imports/api/creature/computation/EffectAggregator.js @@ -25,6 +25,9 @@ export default class EffectAggregator{ case 'base': // Take the largest base value this.base = result > this.base ? result : this.base; + if (effect.statBase){ + this.statBaseValue = result > this.statBaseValue ? result : this.statBaseValue; + } break; case 'add': // Add all adds together diff --git a/app/imports/api/creature/computation/applyToggles.js b/app/imports/api/creature/computation/applyToggles.js new file mode 100644 index 00000000..cdcda3fd --- /dev/null +++ b/app/imports/api/creature/computation/applyToggles.js @@ -0,0 +1,11 @@ +import computeToggle from '/imports/api/creature/computation/computeToggle.js'; + +export default function applyToggles(prop, memo){ + prop.computationDetails.toggleAncestors.forEach(toggleId => { + let toggle = memo.togglesById[toggleId]; + computeToggle(toggle, memo); + if (!toggle.toggleResult){ + prop.computationDetails.disabledByToggle = true; + } + }); +} diff --git a/app/imports/api/creature/computation/combineStat.js b/app/imports/api/creature/computation/combineStat.js index 2ca20994..b8fd76c8 100644 --- a/app/imports/api/creature/computation/combineStat.js +++ b/app/imports/api/creature/computation/combineStat.js @@ -1,5 +1,5 @@ import computeStat from '/imports/api/creature/computation/computeStat.js'; - +import applyToggles from '/imports/api/creature/computation/applyToggles.js'; export default function combineStat(stat, aggregator, memo){ if (stat.type === 'attribute'){ @@ -37,7 +37,13 @@ function combineSkill(stat, aggregator, memo){ stat.proficiency = stat.baseProficiency || 0; for (let i in stat.computationDetails.proficiencies){ let prof = stat.computationDetails.proficiencies[i]; - if (prof.value > stat.proficiency) stat.proficiency = prof.value; + applyToggles(prof, memo); + if ( + !prof.computationDetails.disabledByToggle && + prof.value > stat.proficiency + ){ + stat.proficiency = prof.value; + } } // Get the character's proficiency bonus to apply let profBonusStat = memo.statsByVariableName['proficiencyBonus']; diff --git a/app/imports/api/creature/computation/computeEffect.js b/app/imports/api/creature/computation/computeEffect.js index c95aab23..cad59fc6 100644 --- a/app/imports/api/creature/computation/computeEffect.js +++ b/app/imports/api/creature/computation/computeEffect.js @@ -1,7 +1,25 @@ import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js'; +import applyToggles from '/imports/api/creature/computation/applyToggles.js'; export default function computeEffect(effect, memo){ if (effect.computationDetails.computed) return; + if (effect.computationDetails.busyComputing){ + // Trying to compute this effect again while it is already computing. + // We must be in a dependency loop. + effect.computationDetails.computed = true; + effect.result = NaN; + effect.computationDetails.busyComputing = false; + effect.computationDetails.error = 'dependencyLoop'; + if (Meteor.isClient) console.warn('dependencyLoop', effect); + return; + } + // Before doing any work, mark this effect as busy + effect.computationDetails.busyComputing = true; + + // Apply any toggles + applyToggles(effect, memo); + + // Determine result of effect calculation if (!effect.calculation){ if(effect.operation === 'add' || effect.operation === 'base'){ effect.result = 0; @@ -16,4 +34,5 @@ export default function computeEffect(effect, memo){ effect.result = evaluateCalculation(effect.calculation, memo); } effect.computationDetails.computed = true; + effect.computationDetails.busyComputing = false; } diff --git a/app/imports/api/creature/computation/computeMemo.js b/app/imports/api/creature/computation/computeMemo.js index 2a22dfaa..bd7f1384 100644 --- a/app/imports/api/creature/computation/computeMemo.js +++ b/app/imports/api/creature/computation/computeMemo.js @@ -1,12 +1,19 @@ import { each, forOwn } from 'lodash'; import computeStat from '/imports/api/creature/computation/computeStat.js'; import computeEffect from '/imports/api/creature/computation/computeEffect.js'; +import computeToggle from '/imports/api/creature/computation/computeToggle.js'; export default function computeMemo(memo){ - forOwn(memo.statsByVariableName, (stat) => { + // Compute all stats, even if they are overriden + forOwn(memo.statsById, stat => { computeStat (stat, memo); }); - each(memo.unassignedEffects, (effect) => { + // Compute effects which didn't end up targeting a stat + each(memo.unassignedEffects, effect => { computeEffect(effect, memo); }); + forOwn(memo.togglesById, toggle => { + computeToggle(toggle, memo); + }); + // Compute class levels } diff --git a/app/imports/api/creature/computation/computeStat.js b/app/imports/api/creature/computation/computeStat.js index 8f64cbc8..291fb5dd 100644 --- a/app/imports/api/creature/computation/computeStat.js +++ b/app/imports/api/creature/computation/computeStat.js @@ -1,6 +1,7 @@ import combineStat from '/imports/api/creature/computation/combineStat.js'; import computeEffect from '/imports/api/creature/computation/computeEffect.js'; import EffectAggregator from '/imports/api/creature/computation/EffectAggregator.js'; +import applyToggles from '/imports/api/creature/computation/applyToggles.js'; import { each } from 'lodash'; export default function computeStat(stat, memo){ @@ -18,14 +19,21 @@ export default function computeStat(stat, memo){ } // Before doing any work, mark this stat as busy stat.computationDetails.busyComputing = true; - // Compute and aggregate all the effects - let aggregator = new EffectAggregator(stat, memo) - each(stat.computationDetails.effects, (effect) => { - computeEffect(effect, memo); - aggregator.addEffect(effect); - }); - // Conglomerate all the effects to compute the final stat values - combineStat(stat, aggregator, memo); + // Apply any toggles + applyToggles(stat, memo); + + if (!stat.computationDetails.disabledByToggle){ + // Compute and aggregate all the effects + let aggregator = new EffectAggregator(stat, memo) + each(stat.computationDetails.effects, (effect) => { + computeEffect(effect, memo); + if (!effect.computationDetails.disabledByToggle){ + aggregator.addEffect(effect); + } + }); + // Conglomerate all the effects to compute the final stat values + combineStat(stat, aggregator, memo); + } // Mark the attribute as computed stat.computationDetails.computed = true; stat.computationDetails.busyComputing = false; diff --git a/app/imports/api/creature/computation/computeToggle.js b/app/imports/api/creature/computation/computeToggle.js new file mode 100644 index 00000000..a1689262 --- /dev/null +++ b/app/imports/api/creature/computation/computeToggle.js @@ -0,0 +1,30 @@ +import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js'; + +export default function computeToggle(toggle, memo){ + if (toggle.computationDetails.computed) return; + if (toggle.computationDetails.busyComputing){ + // Trying to compute this effect again while it is already computing. + // We must be in a dependency loop. + toggle.computationDetails.computed = true; + toggle.result = false; + toggle.computationDetails.busyComputing = false; + toggle.computationDetails.error = 'dependencyLoop'; + if (Meteor.isClient) console.warn('dependencyLoop', toggle); + return; + } + // Before doing any work, mark this toggle as busy + toggle.computationDetails.busyComputing = true; + + // Do work + if (toggle.enabled){ + toggle.toggleResult = true; + } else if (!toggle.condition){ + toggle.toggleResult = false; + } else if (Number.isFinite(+toggle.condition)){ + toggle.toggleResult = !!+toggle.condition; + } else { + toggle.toggleResult = evaluateCalculation(toggle.condition, memo); + } + toggle.computationDetails.computed = true; + toggle.computationDetails.busyComputing = false; +} diff --git a/app/imports/api/creature/computation/recomputeCreature.js b/app/imports/api/creature/computation/recomputeCreature.js index 10929f37..6990087f 100644 --- a/app/imports/api/creature/computation/recomputeCreature.js +++ b/app/imports/api/creature/computation/recomputeCreature.js @@ -32,6 +32,7 @@ const calculationPropertyTypes = [ 'effect', 'proficiency', 'classLevel', + 'toggle', ]; /** @@ -71,7 +72,11 @@ const calculationPropertyTypes = [ * - Write the computed results back to the database */ export function recomputeCreatureById(creatureId){ - let props = getActiveProperties(creatureId, {type: {$in: calculationPropertyTypes}}); + let props = getActiveProperties({ + ancestorId: creatureId, + filter: {type: {$in: calculationPropertyTypes}}, + includeUntoggled: true, + }); let computationMemo = new ComputationMemo(props); computeMemo(computationMemo); writeAlteredProperties(computationMemo); diff --git a/app/imports/api/creature/computation/writeAlteredProperties.js b/app/imports/api/creature/computation/writeAlteredProperties.js index 14a407f5..7820b319 100644 --- a/app/imports/api/creature/computation/writeAlteredProperties.js +++ b/app/imports/api/creature/computation/writeAlteredProperties.js @@ -3,51 +3,60 @@ import { isEqual, forOwn } from 'lodash'; import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js'; import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js'; import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js'; +import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js'; import CreatureProperties from '/imports/api/creature/CreatureProperties.js'; +const schemasByType = { + 'skill': ComputedOnlySkillSchema, + 'attribute': ComputedOnlyAttributeSchema, + 'effect': ComputedOnlyEffectSchema, + 'toggle': ComputedOnlyToggleSchema, +}; + export default function writeAlteredProperties(memo){ let bulkWriteOperations = []; // Loop through all properties on the memo - forOwn(memo.originalPropsById, (original, _id) => { - let changed = memo.propsById[_id]; - - let schema; - switch (changed.type){ - case 'skill': - schema = ComputedOnlySkillSchema; - break; - case 'attribute': - schema = ComputedOnlyAttributeSchema; - break; - case 'effect': - schema = ComputedOnlyEffectSchema; - break; - default: - return; + forOwn(memo.propsById, changed => { + let schema = schemasByType[changed.type]; + if (!schema) return; + let extraIds = changed.computationDetails.idsOfSameName; + let ids; + if (extraIds && extraIds.length){ + ids = [changed._id, ...extraIds]; + } else { + ids = [changed._id]; } - let op = undefined; - // Loop through all keys that can be changed by computation - // and compile an operation that sets all those keys - for (let key of schema.objectKeys()){ - if (!isEqual(original[key], changed[key])){ - if (!op) op = newOperation(_id, changed.type); - let value = changed[key]; - if (value === undefined){ - // Unset values that become undefined - addUnsetOp(op, key); - } else { - // Set values that changed to something else - addSetOp(op, key, value); - } + ids.forEach(id => { + let op = undefined; + let original = memo.originalPropsById[id]; + op = addChangedKeysToOp(op, schema.objectKeys(), original, changed); + if (op){ + bulkWriteOperations.push(op); } - } - if (op){ - bulkWriteOperations.push(op); - } + }); }); bulkWriteProperties(bulkWriteOperations); } +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 (!isEqual(original[key], changed[key])){ + if (!op) op = newOperation(original._id, changed.type); + let value = changed[key]; + if (value === undefined){ + // Unset values that become undefined + addUnsetOp(op, key); + } else { + // Set values that changed to something else + addSetOp(op, key, value); + } + } + } + return op; +} + function newOperation(_id, type){ let newOp = { updateOne: { @@ -92,7 +101,8 @@ function bulkWriteProperties(bulkWriteOps){ ); } else { bulkWriteOps.forEach(op => { - CreatureProperties.update(op.updateOne.filter, op.updateOne.update, { + let updateOneOrMany = op.updateOne || op.updateMany; + CreatureProperties.update(updateOneOrMany.filter, updateOneOrMany.update, { // The server code is bypassing collection 2 validation, so do the same // on the client // include this if bypass is off: diff --git a/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js b/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js index 66f141a5..aba990f7 100644 --- a/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js +++ b/app/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js @@ -24,7 +24,10 @@ export const recomputeDamageMultipliers = new ValidatedMethod({ export function recomputeDamageMultipliersById(creatureId){ if (!creatureId) throw 'Creature ID is required'; - let props = getActiveProperties(creatureId, {type: 'damageMultiplier'}); + let props = getActiveProperties({ + ancestorId: creatureId, + filter: {type: 'damageMultiplier'}, + }); // Count of how many weakness, resistances and immunities each damage type has let multipliersByName = {}; diff --git a/app/imports/api/creature/getActiveProperties.js b/app/imports/api/creature/getActiveProperties.js index 49ae5b52..deef0796 100644 --- a/app/imports/api/creature/getActiveProperties.js +++ b/app/imports/api/creature/getActiveProperties.js @@ -1,19 +1,28 @@ import Creatures from '/imports/api/creature/Creatures.js'; import CreatureProperties from '/imports/api/creature/CreatureProperties.js'; -export default function getActiveProperties(ancestorId, filter = {}, options){ +export default function getActiveProperties({ + ancestorId, + filter = {}, + options, + includeUntoggled = false +}){ if (!ancestorId){ throw 'Ancestor Id is required to get active properties' } // First get ids of disabled properties, unequiped items, unapplied buffs - let disabledAncestorIds = CreatureProperties.find({ + let disabledAncestorsFilter = { 'ancestors.id': ancestorId, $or: [ {disabled: true}, {equipped: false}, {applied: false}, ], - }, { + }; + if (!includeUntoggled){ + disabledAncestorsFilter.$or.push({toggleResult: false}); + } + let disabledAncestorIds = CreatureProperties.find(disabledAncestorsFilter, { fields: {_id: 1}, }).map(prop => prop._id); diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index 3ce4bcd5..7861bb91 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -1,6 +1,5 @@ import SimpleSchema from 'simpl-schema'; import ResourcesSchema from '/imports/api/properties/subSchemas/ResourcesSchema.js' -import ResultsSchema from '/imports/api/properties/subSchemas/ResultsSchema.js'; /* * Actions are things a character can do @@ -41,10 +40,6 @@ let ActionSchema = new SimpleSchema({ 'tags.$': { type: String, }, - results: { - type: ResultsSchema, - defaultValue: {}, - }, resources: { type: ResourcesSchema, defaultValue: {}, diff --git a/app/imports/api/properties/Adjustments.js b/app/imports/api/properties/Adjustments.js new file mode 100644 index 00000000..72d99ab1 --- /dev/null +++ b/app/imports/api/properties/Adjustments.js @@ -0,0 +1,27 @@ +import SimpleSchema from 'simpl-schema'; + +const AdjustmentSchema = new SimpleSchema({ + // The roll that determines how much to change the attribute + adjustment: { + type: String, + optional: true, + defaultValue: '1', + }, + // Who this adjustment applies to + target: { + type: String, + defaultValue: 'every', + allowedValues: [ + 'self', // the character who took the action + 'each', // rolled once for `each` target + 'every', // rolled once and applied to `every` target + ], + }, + // The stat this rolls applies to + stat: { + type: String, + optional: true, + }, +}); + +export { AdjustmentSchema }; diff --git a/app/imports/api/properties/Buffs.js b/app/imports/api/properties/Buffs.js index 82b97d46..25ff45f5 100644 --- a/app/imports/api/properties/Buffs.js +++ b/app/imports/api/properties/Buffs.js @@ -1,6 +1,4 @@ import SimpleSchema from 'simpl-schema'; -import { Random } from 'meteor/random'; -import { StoredEffectSchema } from '/imports/api/properties/Effects.js'; let BuffSchema = new SimpleSchema({ name: { @@ -15,20 +13,12 @@ let BuffSchema = new SimpleSchema({ type: String, optional: true, }, -}); - -// The effects in the stored buff need to be resolved to a number before being -// placed on other characters, if they are applied to self, they can remain as -// calculations, provided they don't contain any rolls -let StoredBuffSchema = new SimpleSchema({ - effects: { - type: Array, - defaultValue: [], - }, - 'effects.$': { - type: StoredEffectSchema, - }, - target: { + applied: { + type: Boolean, + defaultValue: false, + index: 1, + }, + target: { type: String, allowedValues: [ 'self', // the character who took the buff @@ -37,21 +27,14 @@ let StoredBuffSchema = new SimpleSchema({ ], defaultValue: 'every', }, -}).extend(BuffSchema); - -let StoredBuffWithIdSchema = new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.Id, - autoValue(){ - if (!this.isSet){ - return Random.id(); - } - }, - }, -}).extend(StoredBuffSchema); +}); let AppliedBuffSchema = new SimpleSchema({ + applied: { + type: Boolean, + defaultValue: true, + index: 1, + }, durationSpent: { type: Number, optional: true, @@ -73,4 +56,4 @@ let AppliedBuffSchema = new SimpleSchema({ }, }).extend(BuffSchema); -export { AppliedBuffSchema, StoredBuffSchema, StoredBuffWithIdSchema }; +export { AppliedBuffSchema, BuffSchema }; diff --git a/app/imports/api/properties/subSchemas/DamageSchema.js b/app/imports/api/properties/Damages.js similarity index 73% rename from app/imports/api/properties/subSchemas/DamageSchema.js rename to app/imports/api/properties/Damages.js index ab9400ab..4aaa2fcb 100644 --- a/app/imports/api/properties/subSchemas/DamageSchema.js +++ b/app/imports/api/properties/Damages.js @@ -1,22 +1,14 @@ import SimpleSchema from 'simpl-schema'; -import { Random } from 'meteor/random'; import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js'; const DamageSchema = new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.Id, - autoValue(){ - if (!this.isSet) return Random.id(); - } - }, // The roll that determines how much to damage the attribute damage: { type: String, optional: true, defaultValue: '1d8 + strength.modifier', }, - // Who this adjustment applies to + // Who this damage applies to target: { type: String, defaultValue: 'every', @@ -33,4 +25,4 @@ const DamageSchema = new SimpleSchema({ }, }); -export default DamageSchema; +export { DamageSchema }; diff --git a/app/imports/api/properties/Effects.js b/app/imports/api/properties/Effects.js index f71fde96..677b3c2d 100644 --- a/app/imports/api/properties/Effects.js +++ b/app/imports/api/properties/Effects.js @@ -1,5 +1,4 @@ import SimpleSchema from 'simpl-schema'; -import { Random } from 'meteor/random'; /* * Effects are reason-value attached to skills and abilities @@ -42,16 +41,6 @@ let EffectSchema = new SimpleSchema({ }, }); -const StoredEffectSchema = new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.Id, - autoValue(){ - if (!this.isSet) return Random.id(); - } - }, -}).extend(EffectSchema); - const ComputedOnlyEffectSchema = new SimpleSchema({ // The computed result of the effect result: { @@ -64,4 +53,4 @@ const ComputedEffectSchema = new SimpleSchema() .extend(ComputedOnlyEffectSchema) .extend(EffectSchema); -export { EffectSchema, StoredEffectSchema, ComputedEffectSchema, ComputedOnlyEffectSchema }; +export { EffectSchema, ComputedEffectSchema, ComputedOnlyEffectSchema }; diff --git a/app/imports/api/properties/Results.js b/app/imports/api/properties/Results.js new file mode 100644 index 00000000..cc094118 --- /dev/null +++ b/app/imports/api/properties/Results.js @@ -0,0 +1,15 @@ +import SimpleSchema from 'simpl-schema'; + +const ResultSchema = new SimpleSchema({ + name: { + type: String, + optional: true, + }, + // Expression of whether or not to apply the children + comparison: { + type: String, + optional: true, + }, +}); + +export { ResultSchema }; diff --git a/app/imports/api/properties/Rolls.js b/app/imports/api/properties/Rolls.js index 5278fb6f..fcb4bfa5 100644 --- a/app/imports/api/properties/Rolls.js +++ b/app/imports/api/properties/Rolls.js @@ -1,5 +1,4 @@ import SimpleSchema from 'simpl-schema'; -import RollResultsSchema from '/imports/api/properties/subSchemas/RollResultsSchema.js' /** * Rolls are children to actions or other rolls, they are triggered with 0 or @@ -34,13 +33,6 @@ let RollSchema = new SimpleSchema({ 'tags.$': { type: String, }, - rollResults: { - type: Array, - defaultValue: [], - }, - 'rollResults.$': { - type: RollResultsSchema, - }, }); export { RollSchema }; diff --git a/app/imports/api/properties/SavingThrows.js b/app/imports/api/properties/SavingThrows.js index ba35f658..4c704ce9 100644 --- a/app/imports/api/properties/SavingThrows.js +++ b/app/imports/api/properties/SavingThrows.js @@ -1,5 +1,4 @@ import SimpleSchema from 'simpl-schema'; -import ResultsSchema from '/imports/api/properties/subSchemas/ResultsSchema.js'; // These are the rolls made when saves are called for // For the saving throw bonus or proficiency, see ./Skills.js @@ -8,18 +7,10 @@ let SavingThrowSchema = new SimpleSchema ({ type: String, optional: true, }, - // The variable name of ability the saving throw relies on - ability: { + // The variable name of ability the save to roll + stat: { type: String, optional: true, - }, - passResults: { - type: ResultsSchema, - defaultValue: {}, - }, - failResults: { - type: ResultsSchema, - defaultValue: {}, }, }); diff --git a/app/imports/api/properties/Toggles.js b/app/imports/api/properties/Toggles.js new file mode 100644 index 00000000..061f86e3 --- /dev/null +++ b/app/imports/api/properties/Toggles.js @@ -0,0 +1,36 @@ +import SimpleSchema from 'simpl-schema'; + +const ToggleSchema = new SimpleSchema({ + name: { + type: String, + optional: true, + }, + disabled: { + type: Boolean, + optional: true, + }, + enabled: { + type: Boolean, + optional: true, + }, + // if neither disabled or enabled, the condition will be run to determine + // if the children of the toggle should be active + condition: { + type: String, + optional: true, + }, +}); + +const ComputedOnlyToggleSchema = new SimpleSchema({ + // The computed result of the effect + toggleResult: { + type: SimpleSchema.oneOf(Number, String, Boolean), + optional: true, + }, +}); + +const ComputedToggleSchema = new SimpleSchema() + .extend(ComputedOnlyToggleSchema) + .extend(ToggleSchema); + +export { ToggleSchema, ComputedOnlyToggleSchema, ComputedToggleSchema }; diff --git a/app/imports/api/properties/computedPropertySchemasIndex.js b/app/imports/api/properties/computedPropertySchemasIndex.js index 5c596bcb..0a741d8c 100644 --- a/app/imports/api/properties/computedPropertySchemasIndex.js +++ b/app/imports/api/properties/computedPropertySchemasIndex.js @@ -1,9 +1,11 @@ import SimpleSchema from 'simpl-schema'; import { ActionSchema } from '/imports/api/properties/Actions.js'; +import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js'; import { AttackSchema } from '/imports/api/properties/Attacks.js'; import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js'; import { AppliedBuffSchema } from '/imports/api/properties/Buffs.js'; import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js'; +import { DamageSchema } from '/imports/api/properties/Damages.js'; import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js'; import { ComputedEffectSchema } from '/imports/api/properties/Effects.js'; import { ExperienceSchema } from '/imports/api/properties/Experiences.js'; @@ -11,21 +13,25 @@ import { FeatureSchema } from '/imports/api/properties/Features.js'; import { FolderSchema } from '/imports/api/properties/Folders.js'; import { NoteSchema } from '/imports/api/properties/Notes.js'; import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js'; +import { ResultSchema } from '/imports/api/properties/Results.js'; import { RollSchema } from '/imports/api/properties/Rolls.js'; import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js'; import { ComputedSkillSchema } from '/imports/api/properties/Skills.js'; import { SlotSchema } from '/imports/api/properties/Slots.js'; import { SpellListSchema } from '/imports/api/properties/SpellLists.js'; import { SpellSchema } from '/imports/api/properties/Spells.js'; +import { ToggleSchema } from '/imports/api/properties/Toggles.js'; import { ContainerSchema } from '/imports/api/properties/Containers.js'; import { ItemSchema } from '/imports/api/properties/Items.js'; const propertySchemasIndex = { action: ActionSchema, + adjustment: AdjustmentSchema, attack: AttackSchema, attribute: ComputedAttributeSchema, buff: AppliedBuffSchema, classLevel: ClassLevelSchema, + damage: DamageSchema, damageMultiplier: DamageMultiplierSchema, effect: ComputedEffectSchema, experience: ExperienceSchema, @@ -33,12 +39,14 @@ const propertySchemasIndex = { folder: FolderSchema, note: NoteSchema, proficiency: ProficiencySchema, + result: ResultSchema, roll: RollSchema, savingThrow: SavingThrowSchema, skill: ComputedSkillSchema, slot: SlotSchema, spellList: SpellListSchema, spell: SpellSchema, + toggle: ToggleSchema, container: ContainerSchema, item: ItemSchema, any: new SimpleSchema({}), diff --git a/app/imports/api/properties/propertySchemasIndex.js b/app/imports/api/properties/propertySchemasIndex.js index 85e30b5e..3b209718 100644 --- a/app/imports/api/properties/propertySchemasIndex.js +++ b/app/imports/api/properties/propertySchemasIndex.js @@ -1,9 +1,11 @@ import SimpleSchema from 'simpl-schema'; import { ActionSchema } from '/imports/api/properties/Actions.js'; +import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js'; import { AttackSchema } from '/imports/api/properties/Attacks.js'; import { AttributeSchema } from '/imports/api/properties/Attributes.js'; import { StoredBuffSchema } from '/imports/api/properties/Buffs.js'; import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js'; +import { DamageSchema } from '/imports/api/properties/Damages.js'; import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js'; import { EffectSchema } from '/imports/api/properties/Effects.js'; import { ExperienceSchema } from '/imports/api/properties/Experiences.js'; @@ -11,21 +13,25 @@ import { FeatureSchema } from '/imports/api/properties/Features.js'; import { FolderSchema } from '/imports/api/properties/Folders.js'; import { NoteSchema } from '/imports/api/properties/Notes.js'; import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js'; +import { ResultSchema } from '/imports/api/properties/Results.js'; import { RollSchema } from '/imports/api/properties/Rolls.js'; import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js'; import { SkillSchema } from '/imports/api/properties/Skills.js'; import { SlotSchema } from '/imports/api/properties/Slots.js'; import { SpellListSchema } from '/imports/api/properties/SpellLists.js'; import { SpellSchema } from '/imports/api/properties/Spells.js'; +import { ToggleSchema } from '/imports/api/properties/Toggles.js'; import { ContainerSchema } from '/imports/api/properties/Containers.js'; import { ItemSchema } from '/imports/api/properties/Items.js'; const propertySchemasIndex = { action: ActionSchema, + adjustment: AdjustmentSchema, attack: AttackSchema, attribute: AttributeSchema, buff: StoredBuffSchema, classLevel: ClassLevelSchema, + damage: DamageSchema, damageMultiplier: DamageMultiplierSchema, effect: EffectSchema, experience: ExperienceSchema, @@ -33,12 +39,14 @@ const propertySchemasIndex = { folder: FolderSchema, note: NoteSchema, proficiency: ProficiencySchema, + result: ResultSchema, roll: RollSchema, savingThrow: SavingThrowSchema, skill: SkillSchema, slot: SlotSchema, spellList: SpellListSchema, spell: SpellSchema, + toggle: ToggleSchema, container: ContainerSchema, item: ItemSchema, any: new SimpleSchema({}), diff --git a/app/imports/api/properties/subSchemas/ResultsSchema.js b/app/imports/api/properties/subSchemas/ResultsSchema.js deleted file mode 100644 index 9519b3f9..00000000 --- a/app/imports/api/properties/subSchemas/ResultsSchema.js +++ /dev/null @@ -1,36 +0,0 @@ -import SimpleSchema from 'simpl-schema'; - -import AdjustmentSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js'; -import DamageSchema from '/imports/api/properties/subSchemas/DamageSchema.js'; -import { StoredBuffWithIdSchema } from '/imports/api/properties/Buffs.js'; - -let ResultsSchema = new SimpleSchema({ - // Adjustments applied when taking this action - // Ideally, if these adjustments can't be made, the action should be unusable - adjustments: { - type: Array, - defaultValue: [], - }, - 'adjustments.$': { - type: AdjustmentSchema, - }, - // Damage is done to hitpoints or hitpoint-like stats - // has a damage type, can be mitigated by resistances, etc. - damages: { - type: Array, - defaultValue: [], - }, - 'damages.$': { - type: DamageSchema, - }, - // Buffs applied when taking this action - buffs: { - type: Array, - defaultValue: [], - }, - 'buffs.$': { - type: StoredBuffWithIdSchema, - }, -}); - -export default ResultsSchema; diff --git a/app/imports/constants/PROPERTIES.js b/app/imports/constants/PROPERTIES.js index 90e49ce2..9c8e6974 100644 --- a/app/imports/constants/PROPERTIES.js +++ b/app/imports/constants/PROPERTIES.js @@ -75,6 +75,10 @@ const PROPERTIES = Object.freeze({ icon: 'category', name: 'Item' }, + toggle: { + icon: 'power_settings_new', + name: 'Toggle' + }, }); export default PROPERTIES; diff --git a/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue index dd33bf5d..8048d97d 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -276,13 +276,14 @@ import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue'; import ActionListTile from '/imports/ui/properties/components/actions/ActionListTile.vue'; import AttackListTile from '/imports/ui/properties/components/actions/AttackListTile.vue'; + import getActiveProperties from '/imports/api/creature/getActiveProperties.js'; - const getProperties = function(creatureId, filter = {}){ - filter['ancestors.id'] = creatureId; - filter.removed = {$ne: true}; - return CreatureProperties.find(filter, { - sort: {order: 1} - }); + const getProperties = function(creatureId, filter){ + return getActiveProperties({ + ancestorId: creatureId, + filter, + options: {sort: {order: 1}}, + }); }; const getAttributeOfType = function(creatureId, type){ diff --git a/app/imports/ui/properties/forms/ResultsForm.vue b/app/imports/ui/properties/forms/ResultsForm.vue index c850486b..308463de 100644 --- a/app/imports/ui/properties/forms/ResultsForm.vue +++ b/app/imports/ui/properties/forms/ResultsForm.vue @@ -1,6 +1,9 @@