diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 44fc93e9..7ed69d19 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -23,23 +23,26 @@ const linkDependenciesByType = { toggle: linkToggle, } -export default function linkTypeDependencies(dependencyGraph, prop, computation){ +export default function linkTypeDependencies(dependencyGraph, prop, computation) { linkDependenciesByType[prop.type]?.(dependencyGraph, prop, computation); } -function dependOnCalc({dependencyGraph, prop, key}){ +function dependOnCalc({ dependencyGraph, prop, key }) { let calc = get(prop, key); if (!calc) return; - if (calc.type !== '_calculation'){ + if (calc.type !== '_calculation') { throw `Expected calculation got ${calc.type}` } dependencyGraph.addLink(prop._id, `${prop._id}.${key}`, 'calculation'); } -function linkAction(dependencyGraph, prop, {propsById}){ +function linkAction(dependencyGraph, prop, { propsById }) { + if (prop.variableName) { + dependencyGraph.addLink(prop.variableName, prop._id, 'eventDefinition'); + } // The action depends on its attack roll and uses calculations - dependOnCalc({dependencyGraph, prop, key: 'attackRoll'}); - dependOnCalc({dependencyGraph, prop, key: 'uses'}); + dependOnCalc({ dependencyGraph, prop, key: 'attackRoll' }); + dependOnCalc({ dependencyGraph, prop, key: 'uses' }); // Link the resources the action uses if (!prop.resources) return; @@ -47,7 +50,7 @@ function linkAction(dependencyGraph, prop, {propsById}){ prop.resources.itemsConsumed.forEach((itemConsumed, index) => { if (!itemConsumed.itemId) return; const item = propsById[itemConsumed.itemId]; - if (!item || item.inactive){ + if (!item || item.inactive) { // Unlink if the item doesn't exist or is inactive itemConsumed.itemId = undefined; return; @@ -79,48 +82,48 @@ function linkAction(dependencyGraph, prop, {propsById}){ }); } -function linkAdjustment(dependencyGraph, prop){ +function linkAdjustment(dependencyGraph, prop) { // Adjustment depends on its amount - dependOnCalc({dependencyGraph, prop, key: 'amount'}); + dependOnCalc({ dependencyGraph, prop, key: 'amount' }); } -function linkAttribute(dependencyGraph, prop){ +function linkAttribute(dependencyGraph, prop) { linkVariableName(dependencyGraph, prop); // Depends on spellSlotLevel - dependOnCalc({dependencyGraph, prop, key: 'spellSlotLevel'}); + dependOnCalc({ dependencyGraph, prop, key: 'spellSlotLevel' }); // Depends on base value - dependOnCalc({dependencyGraph, prop, key: 'baseValue'}); + dependOnCalc({ dependencyGraph, prop, key: 'baseValue' }); // hit dice depend on constitution - if (prop.attributeType === 'hitDice'){ + if (prop.attributeType === 'hitDice') { dependencyGraph.addLink(prop._id, 'constitution', 'hitDiceConMod'); } } -function linkBranch(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'condition'}); +function linkBranch(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'condition' }); } -function linkBuff(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'duration'}); +function linkBuff(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'duration' }); } function linkClassLevel(dependencyGraph, prop) { if (prop.inactive) return; // The variableName of the prop depends on the prop - if (prop.variableName && prop.level){ + if (prop.variableName && prop.level) { dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel'); // The level variable depends on the class variableName variable let existingLevelLink = dependencyGraph.getLink('level', prop.variableName); - if (!existingLevelLink){ + if (!existingLevelLink) { dependencyGraph.addLink('level', prop.variableName, 'level'); } } } -function linkDamage(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'amount'}); +function linkDamage(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'amount' }); } function linkEffects(dependencyGraph, prop, computation) { @@ -132,7 +135,7 @@ function linkEffects(dependencyGraph, prop, computation) { if (prop.inactive) { // Inactive effects apply to no stats return; - } else if (prop.targetByTags){ + } else if (prop.targetByTags) { getEffectTagTargets(prop, computation).forEach(targetId => { const targetProp = computation.propsById[targetId]; if ( @@ -147,8 +150,8 @@ function linkEffects(dependencyGraph, prop, computation) { // Otherwise target a field on that property const key = prop.targetField || getDefaultCalculationField(targetProp); const calcObj = get(targetProp, key); - if (calcObj && calcObj.calculation){ - dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id , 'effect'); + if (calcObj && calcObj.calculation) { + dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'effect'); } } }); @@ -161,14 +164,14 @@ function linkEffects(dependencyGraph, prop, computation) { } // Returns an array of IDs of the properties the effect targets -function getEffectTagTargets(effect, computation){ +function getEffectTagTargets(effect, computation) { let targets = getTargetListFromTags(effect.targetTags, computation); let notIds = []; - if (effect.extraTags){ + if (effect.extraTags) { effect.extraTags.forEach(ex => { if (ex.operation === 'OR') { targets = union(targets, getTargetListFromTags(ex.tags, computation)); - } else if (ex.operation === 'NOT'){ + } else if (ex.operation === 'NOT') { ex.tags.forEach(tag => { const idList = computation.propsWithTag[tag]; if (idList) { @@ -181,7 +184,7 @@ function getEffectTagTargets(effect, computation){ return difference(targets, notIds); } -function getTargetListFromTags(tags, computation){ +function getTargetListFromTags(tags, computation) { const targetTagIdLists = []; if (!tags) return []; tags.forEach(tag => { @@ -192,8 +195,8 @@ function getTargetListFromTags(tags, computation){ return targets; } -function getDefaultCalculationField(prop){ - switch (prop.type){ +function getDefaultCalculationField(prop) { + switch (prop.type) { case 'action': return 'attackRoll'; case 'adjustment': return 'amount'; case 'attribute': return 'baseValue'; @@ -223,13 +226,13 @@ function getDefaultCalculationField(prop){ } } -function linkRoll(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'roll'}); +function linkRoll(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'roll' }); } -function linkVariableName(dependencyGraph, prop){ +function linkVariableName(dependencyGraph, prop) { // The variableName of the prop depends on the prop if the prop is active - if (prop.variableName && !prop.inactive){ + if (prop.variableName && !prop.inactive) { dependencyGraph.addLink(prop.variableName, prop._id, 'definition'); } } @@ -243,7 +246,7 @@ function linkDamageMultiplier(dependencyGraph, prop) { }); } -function linkPointBuy(dependencyGraph, prop){ +function linkPointBuy(dependencyGraph, prop) { dependOnCalc({ dependencyGraph, prop, key: 'min' }); dependOnCalc({ dependencyGraph, prop, key: 'max' }); dependOnCalc({ dependencyGraph, prop, key: 'cost' }); @@ -265,7 +268,7 @@ function linkPointBuy(dependencyGraph, prop){ if (prop.inactive) return; } -function linkProficiencies(dependencyGraph, prop){ +function linkProficiencies(dependencyGraph, prop) { // The stats depend on the proficiency if (prop.inactive) return; prop.stats.forEach(statName => { @@ -274,36 +277,36 @@ function linkProficiencies(dependencyGraph, prop){ }); } -function linkSavingThrow(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'dc'}); +function linkSavingThrow(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'dc' }); } -function linkSkill(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){ + if (prop.ability) { dependencyGraph.addLink(prop._id, prop.ability, 'skillAbilityScore'); } // Skills depend on the creature's proficiencyBonus dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus'); } -function linkSlot(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'quantityExpected'}); - dependOnCalc({dependencyGraph, prop, key: 'slotCondition'}); +function linkSlot(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'quantityExpected' }); + dependOnCalc({ dependencyGraph, prop, key: 'slotCondition' }); } -function linkSpellList(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'maxPrepared'}); - dependOnCalc({dependencyGraph, prop, key: 'attackRollBonus'}); - dependOnCalc({dependencyGraph, prop, key: 'dc'}); +function linkSpellList(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'maxPrepared' }); + dependOnCalc({ dependencyGraph, prop, key: 'attackRollBonus' }); + dependOnCalc({ dependencyGraph, prop, key: 'dc' }); } -function linkToggle(dependencyGraph, prop){ +function linkToggle(dependencyGraph, prop) { linkVariableName(dependencyGraph, prop); - dependOnCalc({dependencyGraph, prop, key: 'condition'}); + dependOnCalc({ dependencyGraph, prop, key: 'condition' }); } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index c52dec00..663922a5 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -1,8 +1,8 @@ -export default function computeAction(computation, node){ +export default function computeAction(computation, node) { const prop = node.data; - if (prop.uses){ + if (prop.uses) { prop.usesLeft = prop.uses.value - (prop.usesUsed || 0); - if (!prop.usesLeft){ + if (!prop.usesLeft) { prop.insufficientResources = true; } } @@ -10,19 +10,19 @@ export default function computeAction(computation, node){ if (!prop.resources) return; prop.resources.itemsConsumed.forEach(itemConsumed => { if (!itemConsumed.itemId) return; - if (itemConsumed.available < itemConsumed.quantity?.value){ + if (itemConsumed.available < itemConsumed.quantity?.value) { prop.insufficientResources = true; } }); prop.resources.attributesConsumed.forEach(attConsumed => { if (!attConsumed.variableName) return; - if (attConsumed.available < attConsumed.quantity?.value){ + if (attConsumed.available < attConsumed.quantity?.value) { prop.insufficientResources = true; } }); } -function computeResources(computation, node){ +function computeResources(computation, node) { const resources = node.data?.resources; if (!resources) return; resources.attributesConsumed.forEach(attConsumed => { diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js index 279ad3d6..5ef7d0be 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js @@ -7,7 +7,7 @@ import computeVariableAsToggle from './computeVariable/computeVariableAsToggle.j import computeImplicitVariable from './computeVariable/computeImplicitVariable.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; -export default function computeVariable(computation, node){ +export default function computeVariable(computation, node) { const scope = computation.scope; if (!node.data) node.data = {}; aggregateLinks(computation, node); @@ -15,7 +15,7 @@ export default function computeVariable(computation, node){ // Don't add to the scope if the node id is not a legitimate variable name // Without this `some.thing` could break the entire sheet as a database key if (!VARIABLE_NAME_REGEX.test(node.id)) return; - if (node.data.definingProp){ + if (node.data.definingProp) { // Add the defining variable to the scope scope[node.id] = node.data.definingProp } else { @@ -24,7 +24,7 @@ export default function computeVariable(computation, node){ } } -function aggregateLinks(computation, node){ +function aggregateLinks(computation, node) { computation.dependencyGraph.forEachLinkedNode( node.id, (linkedNode, link) => { @@ -32,11 +32,12 @@ function aggregateLinks(computation, node){ // Ignore inactive props if (linkedNode.data.inactive) return; // Apply all the aggregations - let arg = {node, linkedNode, link, computation}; + let arg = { node, linkedNode, link, computation }; aggregate.classLevel(arg); aggregate.damageMultiplier(arg); aggregate.definition(arg); aggregate.effect(arg); + aggregate.eventDefinition(arg); aggregate.inventory(arg); aggregate.proficiency(arg); }, @@ -44,7 +45,7 @@ function aggregateLinks(computation, node){ ); } -function combineAggregations(computation, node){ +function combineAggregations(computation, node) { combineMultiplierAggregator(node); node.data.overridenProps?.forEach(prop => { computeVariableProp(computation, node, prop); @@ -52,51 +53,51 @@ function combineAggregations(computation, node){ computeVariableProp(computation, node, node.data.definingProp); } -function computeVariableProp(computation, node, prop){ +function computeVariableProp(computation, node, prop) { if (!prop) return; // Combine damage multipliers in all props so that they can't be overridden - if (node.data.immunity){ + if (node.data.immunity) { prop.immunity = node.data.immunity; prop.immunities = node.data.immunities; } - if (node.data.resistance){ + if (node.data.resistance) { prop.resistance = node.data.resistance; prop.resistances = node.data.resistances; } - if (node.data.vulnerability){ + if (node.data.vulnerability) { prop.vulnerability = node.data.vulnerability; prop.vulnerabilities = node.data.vulnerabilities; } - if (prop.type === 'attribute'){ + if (prop.type === 'attribute') { computeVariableAsAttribute(computation, node, prop); - } else if (prop.type === 'skill'){ + } else if (prop.type === 'skill') { computeVariableAsSkill(computation, node, prop); - } else if (prop.type === 'constant'){ + } else if (prop.type === 'constant') { computeVariableAsConstant(computation, node, prop); - } else if (prop.type === 'class'){ + } else if (prop.type === 'class') { computeVariableAsClass(computation, node, prop); - } else if (prop.type === 'toggle'){ + } else if (prop.type === 'toggle') { computeVariableAsToggle(computation, node, prop); } } -function combineMultiplierAggregator(node){ +function combineMultiplierAggregator(node) { // get a reference to the aggregator const aggregator = node.data.multiplierAggregator; if (!aggregator) return; // Combine - if (aggregator.immunities?.length){ + if (aggregator.immunities?.length) { node.data.immunity = true; node.data.immunities = aggregator.immunities; } - if (aggregator.resistances?.length){ + if (aggregator.resistances?.length) { node.data.resistance = true; node.data.resistances = aggregator.resistances; } - if (aggregator.vulnerabilities?.length){ + if (aggregator.vulnerabilities?.length) { node.data.vulnerability = true; node.data.vulnerabilities = aggregator.vulnerabilities; } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEventDefinition.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEventDefinition.js new file mode 100644 index 00000000..f36cf5b9 --- /dev/null +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEventDefinition.js @@ -0,0 +1,22 @@ + +export default function aggregateEventDefinition({ node, linkedNode, link }) { + // Look at all event definition links + if (link.data !== 'eventDefinition') return; + + // Store which property is THE defining event and which are overridden + const prop = linkedNode.data; + // get current defining event + const definingEvent = node.data.definingEvent; + // Find the last defining event + if ( + !definingEvent || + prop.order > definingEvent.order + ) { + // override the current defining prop + if (definingEvent) definingEvent.overridden = true; + // set this prop as the new defining prop + node.data.definingEvent = prop; + } else { + prop.overridden = true; + } +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js index 4b455afa..3a617494 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js @@ -1,6 +1,7 @@ import definition from './aggregateDefinition.js'; import damageMultiplier from './aggregateDamageMultiplier.js'; import effect from './aggregateEffect.js'; +import eventDefinition from './aggregateEventDefinition.js'; import proficiency from './aggregateProficiency.js'; import classLevel from './aggregateClassLevel.js'; import inventory from './aggregateInventory.js'; @@ -10,6 +11,7 @@ export default Object.freeze({ damageMultiplier, definition, effect, + eventDefinition, inventory, proficiency, }); diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index 99910081..d4a5c4b4 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -162,6 +162,12 @@ const ComputedOnlyActionSchema = createPropertySchema({ optional: true, removeBeforeCompute: true, }, + // Denormalised tag if event is overridden by one with the same variable name + overridden: { + type: Boolean, + optional: true, + removeBeforeCompute: true, + }, // Resources resources: { type: Object, diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue index 93dff4e1..b5254836 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -461,7 +461,7 @@ const propertyHandlers = { return { propPath: 'toggle' }; }, action(prop) { - if (prop.actionType === 'event') { + if (prop.actionType === 'event' && !prop.overridden) { return { propPath: 'event' }; } return { propPath: null };