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/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/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/computedOnlyPropertySchemasIndex.js b/app/imports/api/properties/computedOnlyPropertySchemasIndex.js index d490e4e3..c81f6876 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'; @@ -32,6 +33,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 9084e3b7..25b480b0 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'; @@ -32,6 +33,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 cc4b77ee..23fa4bf4 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'; @@ -32,6 +33,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 c2aa3be1..b0a91040 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/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue index 85c5178e..48b67bf5 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -46,7 +46,7 @@ {{ buff.name }} - + - - - - + + + + + + + + + @@ -60,12 +72,6 @@ export default { mixins: [propertyFormMixin], - props: { - parentTarget: { - type: String, - default: undefined, - }, - }, data(){return { targetOptions: [ { diff --git a/app/imports/ui/properties/forms/BuffRemoverForm.vue b/app/imports/ui/properties/forms/BuffRemoverForm.vue new file mode 100644 index 00000000..f98d7adc --- /dev/null +++ b/app/imports/ui/properties/forms/BuffRemoverForm.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/app/imports/ui/properties/forms/shared/propertyFormIndex.js b/app/imports/ui/properties/forms/shared/propertyFormIndex.js index 1aa8f42e..e8d6eb9f 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'); @@ -31,6 +32,7 @@ export default { adjustment: AdjustmentForm, attribute: AttributeForm, buff: BuffForm, + buffRemover: BuffRemoverForm, branch: BranchForm, constant: ConstantForm, container: ContainerForm, diff --git a/app/imports/ui/properties/viewers/BuffRemoverViewer.vue b/app/imports/ui/properties/viewers/BuffRemoverViewer.vue new file mode 100644 index 00000000..89097b7e --- /dev/null +++ b/app/imports/ui/properties/viewers/BuffRemoverViewer.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js b/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js index 572bfad0..cef19b01 100644 --- a/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js +++ b/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js @@ -2,6 +2,7 @@ const ActionViewer = () => import ('/imports/ui/properties/viewers/ActionViewer. const AdjustmentViewer = () => import ('/imports/ui/properties/viewers/AdjustmentViewer.vue'); const AttributeViewer = () => import ('/imports/ui/properties/viewers/AttributeViewer.vue'); const BuffViewer = () => import ('/imports/ui/properties/viewers/BuffViewer.vue'); +const BuffRemoverViewer = () => import ('/imports/ui/properties/viewers/BuffRemoverViewer.vue'); const BranchViewer = () => import ('/imports/ui/properties/viewers/BranchViewer.vue'); const ContainerViewer = () => import ('/imports/ui/properties/viewers/ContainerViewer.vue'); const ClassViewer = () => import ('/imports/ui/properties/viewers/ClassViewer.vue'); @@ -31,6 +32,7 @@ export default { adjustment: AdjustmentViewer, attribute: AttributeViewer, buff: BuffViewer, + buffRemover: BuffRemoverViewer, branch: BranchViewer, container: ContainerViewer, class: ClassViewer,