diff --git a/app/.meteor/packages b/app/.meteor/packages index 06788bcc..d2c043aa 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -3,7 +3,6 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -zegenie:redis-oplog accounts-password@2.3.4 random@1.2.1 underscore@1.0.13 diff --git a/app/.meteor/versions b/app/.meteor/versions index 578fce81..e5431969 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -124,5 +124,4 @@ underscore@1.0.13 url@1.3.2 webapp@1.13.5 webapp-hashing@1.1.1 -zegenie:redis-oplog@2.0.16 zer0th:meteor-vuetify-loader@0.1.41 diff --git a/app/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary.js b/app/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary.js index 46e7d024..b46205b9 100644 --- a/app/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary.js +++ b/app/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary.js @@ -132,6 +132,9 @@ function insertNodeFromProperty(propId, ancestors, order, method) { prop.order = order; } + // Clean the props + props = cleanProps(props); + // Insert the props as library nodes LibraryNodes.batchInsert(props); return prop; @@ -186,4 +189,11 @@ function assertSourceLibraryCopyPermission(props, method) { }); } +function cleanProps(props) { + return props.map(prop => { + let schema = LibraryNodes.simpleSchema(prop); + return schema.clean(prop); + }); +} + export default copyPropertyToLibrary; diff --git a/app/imports/api/engine/actions/applyProperty.js b/app/imports/api/engine/actions/applyProperty.js index 4dc1cb79..18f35d32 100644 --- a/app/imports/api/engine/actions/applyProperty.js +++ b/app/imports/api/engine/actions/applyProperty.js @@ -19,6 +19,7 @@ const applyPropertyByType = { damage, folder, note, + propertySlot: folder, roll, savingThrow, spell: action, @@ -26,6 +27,7 @@ const applyPropertyByType = { }; export default function applyProperty(node, actionContext, ...rest) { + if (node.node.deactivatedByToggle) return; actionContext.scope[`#${node.node.type}`] = node.node; applyPropertyByType[node.node.type]?.(node, actionContext, ...rest); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 3aa87df9..115d1998 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -3,6 +3,7 @@ import recalculateCalculation from './shared/recalculateCalculation.js'; import rollDice from '/imports/parser/rollDice.js'; import applyProperty from '../applyProperty.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; import numberToSignedString from '/imports/api/utility/numberToSignedString.js'; @@ -187,11 +188,6 @@ function applyCrits(value, scope) { return { criticalHit, criticalMiss }; } -function applyChildren(node, actionContext) { - applyNodeTriggers(node, 'after', actionContext); - node.children.forEach(child => applyProperty(child, actionContext)); -} - function spendResources(prop, actionContext) { // Check Uses if (prop.usesLeft <= 0) { @@ -276,9 +272,10 @@ function spendResources(prop, actionContext) { recalculateCalculation(attConsumed.quantity, actionContext); if (!attConsumed.quantity?.value) return; + if (!attConsumed.variableName) return; let stat = actionContext.scope[attConsumed.variableName]; if (!stat) { - spendLog.push(stat.name + ': ' + ' not found'); + spendLog.push(attConsumed.variableName + ': ' + ' not found'); return; } damagePropertyWork({ diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js index ede94665..5ca17972 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js @@ -1,9 +1,9 @@ -import applyProperty from '../applyProperty.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import recalculateCalculation from './shared/recalculateCalculation.js'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; -export default function applyAdjustment(node, actionContext){ +export default function applyAdjustment(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; const damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets; @@ -39,7 +39,7 @@ export default function applyAdjustment(node, actionContext){ if (!prop.silent) actionContext.addLog({ name: 'Attribute damage', value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + - ` ${value}`, + ` ${value}`, inline: true, }); }); @@ -47,15 +47,10 @@ export default function applyAdjustment(node, actionContext){ if (!prop.silent) actionContext.addLog({ name: 'Attribute damage', value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + - ` ${value}`, + ` ${value}`, inline: true, }); } return applyChildren(node, actionContext); } - -function applyChildren(node, actionContext){ - applyNodeTriggers(node, 'after', actionContext); - node.children.forEach(child => applyProperty(child, actionContext)); -} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js index d48ebc01..8888c461 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js @@ -1,21 +1,18 @@ import applyProperty from '../applyProperty.js'; import recalculateCalculation from './shared/recalculateCalculation.js'; import rollDice from '/imports/parser/rollDice.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; export default function applyBranch(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); - const applyChildren = function () { - applyNodeTriggers(node, 'after', actionContext); - node.children.forEach(child => applyProperty(child, actionContext)); - }; const scope = actionContext.scope; const targets = actionContext.targets; const prop = node.node; switch (prop.branchType) { case 'if': recalculateCalculation(prop.condition, actionContext); - if (prop.condition?.value) applyChildren(); + if (prop.condition?.value) applyChildren(node, actionContext); break; case 'index': if (node.children.length) { @@ -32,30 +29,31 @@ export default function applyBranch(node, actionContext) { if (index > node.children.length) index = node.children.length; applyNodeTriggers(node, 'after', actionContext); applyProperty(node.children[index - 1], actionContext); + applyNodeTriggers(node, 'afterChildren', actionContext); } break; case 'hit': if (scope['~attackHit']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On hit**' }); - applyChildren(); + applyChildren(node, actionContext); } break; case 'miss': if (scope['~attackMiss']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On miss**' }); - applyChildren(); + applyChildren(node, actionContext); } break; case 'failedSave': if (scope['~saveFailed']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On failed save**' }); - applyChildren(); + applyChildren(node, actionContext); } break; case 'successfulSave': if (scope['~saveSucceeded']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On save**', }); - applyChildren(); + applyChildren(node, actionContext); } break; case 'random': @@ -63,6 +61,7 @@ export default function applyBranch(node, actionContext) { let index = rollDice(1, node.children.length)[0] - 1; applyNodeTriggers(node, 'after', actionContext); applyProperty(node.children[index], actionContext); + applyNodeTriggers(node, 'afterChildren', actionContext); } break; case 'eachTarget': @@ -71,9 +70,10 @@ export default function applyBranch(node, actionContext) { applyNodeTriggers(node, 'after', actionContext); actionContext.targets = [target] node.children.forEach(child => applyProperty(child, actionContext)); + applyNodeTriggers(node, 'afterChildren', actionContext); }); } else { - applyChildren(); + applyChildren(node, actionContext); } break; } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js index a755a001..49adf4eb 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js @@ -77,6 +77,7 @@ export default function applyBuff(node, actionContext) { } }); applyNodeTriggers(node, 'after', actionContext); + applyNodeTriggers(node, 'afterChildren', actionContext); // Don't apply the children of the buff, they get copied to the target instead } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js index 86b60949..a9915bdf 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js @@ -5,6 +5,7 @@ import { getProperyAncestors, getPropertiesOfType } from '/imports/api/engine/lo 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'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; export default function applyBuffRemover(node, actionContext) { // Apply triggers @@ -13,7 +14,7 @@ export default function applyBuffRemover(node, actionContext) { const prop = node.node; // Log Name - if (prop.name && !prop.silent){ + if (prop.name && !prop.silent) { actionContext.addLog({ name: prop.name }); } @@ -53,11 +54,7 @@ export default function applyBuffRemover(node, actionContext) { } } } - - // Apply triggers - applyNodeTriggers(node, 'after', actionContext); - // Apply children - node.children.forEach(child => applyProperty(child, actionContext)); + applyChildren(node, actionContext); } function removeBuff(buff, actionContext, prop) { diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index a622989e..a6b1902a 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -1,5 +1,5 @@ import { some, intersection, difference, remove, includes } from 'lodash'; -import applyProperty from '../applyProperty.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js'; import resolve, { Context, toString } from '/imports/parser/resolve.js'; import logErrors from './shared/logErrors.js'; @@ -13,10 +13,6 @@ import getEffectivePropTags from '/imports/api/engine/computation/utility/getEff export default function applyDamage(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); - const applyChildren = function () { - applyNodeTriggers(node, 'after', actionContext); - node.children.forEach(child => applyProperty(child, actionContext)); - }; const prop = node.node; const scope = actionContext.scope; @@ -66,7 +62,7 @@ export default function applyDamage(node, actionContext) { // If we didn't end up with a constant of finite amount, give up if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) { - return applyChildren(); + return applyChildren(node, actionContext); } // Round the damage to a whole number @@ -133,7 +129,7 @@ export default function applyDamage(node, actionContext) { value: logValue.join('\n'), inline: true, }); - return applyChildren(); + return applyChildren(node, actionContext); } function applyDamageMultipliers({ target, damage, damageProp, logValue }) { diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyFolder.js b/app/imports/api/engine/actions/applyPropertyByType/applyFolder.js index 0965f56d..ea216685 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyFolder.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyFolder.js @@ -1,11 +1,9 @@ -import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js'; -import applyProperty from '../applyProperty.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; export default function applyFolder(node, actionContext) { // Apply triggers applyNodeTriggers(node, 'before', actionContext); - applyNodeTriggers(node, 'after', actionContext); // Apply children - node.children.forEach(child => applyProperty(child, actionContext)); + applyChildren(node, actionContext); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyNote.js b/app/imports/api/engine/actions/applyPropertyByType/applyNote.js index 0d5f9e84..332d93dc 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyNote.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyNote.js @@ -1,27 +1,25 @@ import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js'; -import applyProperty from '../applyProperty.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; -export default function applyNote(node, actionContext){ +export default function applyNote(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; // Log Name, summary let content = { name: prop.name }; - if (prop.summary?.text){ + if (prop.summary?.text) { recalculateInlineCalculations(prop.summary, actionContext); content.value = prop.summary.value; } - if (content.name || content.value){ + if (content.name || content.value) { actionContext.addLog(content); } // Log description - if (prop.description?.text){ + if (prop.description?.text) { recalculateInlineCalculations(prop.description, actionContext); - actionContext.addLog({value: prop.description.value}); + actionContext.addLog({ value: prop.description.value }); } - // Apply triggers - applyNodeTriggers(node, 'after', actionContext); // Apply children - node.children.forEach(child => applyProperty(child, actionContext)); + applyChildren(node, actionContext); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js index 68892231..d3b7bf12 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js @@ -1,4 +1,4 @@ -import applyProperty from '../applyProperty.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import logErrors from './shared/logErrors.js'; import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js'; import resolve, { toString } from '/imports/parser/resolve.js'; @@ -8,11 +8,6 @@ export default function applyRoll(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; - const applyChildren = function () { - applyNodeTriggers(node, 'after', actionContext); - node.children.forEach(child => applyProperty(child, actionContext)); - }; - if (prop.roll?.calculation) { const logValue = []; @@ -42,7 +37,7 @@ export default function applyRoll(node, actionContext) { // If we didn't end up with a constant or a number of finite value, give up if (reduced?.parseType !== 'constant' || (reduced.valueType === 'number' && !isFinite(reduced.value))) { - return applyChildren(); + return applyChildren(node, actionContext); } const value = reduced.value; @@ -57,5 +52,5 @@ export default function applyRoll(node, actionContext) { }); } } - return applyChildren(); + return applyChildren(node, actionContext); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index e0803109..ae2f878e 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -2,6 +2,7 @@ import rollDice from '/imports/parser/rollDice.js'; import recalculateCalculation from './shared/recalculateCalculation.js'; import applyProperty from '../applyProperty.js'; import numberToSignedString from '/imports/api/utility/numberToSignedString.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; import { applyUnresolvedEffects } from '/imports/api/engine/actions/doCheck.js'; @@ -34,8 +35,7 @@ export default function applySavingThrow(node, actionContext) { if (!saveTargets?.length) { scope['~saveFailed'] = { value: true }; scope['~saveSucceeded'] = { value: true }; - applyNodeTriggers(node, 'after', actionContext); - return node.children.forEach(child => applyProperty(child, actionContext)); + return applyChildren(node, actionContext); } // Each target makes the saving throw @@ -45,10 +45,9 @@ export default function applySavingThrow(node, actionContext) { delete scope['~saveDiceRoll']; delete scope['~saveRoll']; - const applyChildren = function () { - actionContext.targets = [target] - applyNodeTriggers(node, 'after', actionContext); - node.children.forEach(child => applyProperty(child, actionContext)); + const applyChildrenToTarget = function () { + actionContext.targets = [target]; + return applyChildren(node, actionContext); }; const save = target.variables[prop.stat]; @@ -58,7 +57,7 @@ export default function applySavingThrow(node, actionContext) { name: 'Saving throw error', value: 'No saving throw found: ' + prop.stat, }); - return applyChildren(); + return applyChildrenToTarget(); } let rollModifierText = numberToSignedString(save.value, true); @@ -105,7 +104,7 @@ export default function applySavingThrow(node, actionContext) { value: resultPrefix + '\n**' + result + '**', inline: true, }); - return applyChildren(); + return applyChildrenToTarget(); }); // reset the targets after the save to each child actionContext.targets = originalTargets; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js b/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js index be80b012..47ec3e08 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyToggle.js @@ -1,13 +1,12 @@ -import applyProperty from '../applyProperty.js'; import recalculateCalculation from './shared/recalculateCalculation.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import applyChildren from '/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js'; -export default function applyToggle(node, actionContext){ +export default function applyToggle(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; recalculateCalculation(prop.condition, actionContext); if (prop.condition?.value) { - applyNodeTriggers(node, 'after', actionContext); - return node.children.forEach(child => applyProperty(child, actionContext)); + return applyChildren(node, actionContext); } } diff --git a/app/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js b/app/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js new file mode 100644 index 00000000..9c5c0cbd --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/shared/applyChildren.js @@ -0,0 +1,8 @@ +import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import applyProperty from '/imports/api/engine/actions/applyProperty.js'; + +export default function applyChildren(node, actionContext) { + applyNodeTriggers(node, 'after', actionContext); + node.children.forEach(child => applyProperty(child, actionContext)); + applyNodeTriggers(node, 'afterChildren', actionContext); +} diff --git a/app/imports/api/engine/computation/buildComputation/linkInventory.js b/app/imports/api/engine/computation/buildComputation/linkInventory.js index ae092bd5..7961e4df 100644 --- a/app/imports/api/engine/computation/buildComputation/linkInventory.js +++ b/app/imports/api/engine/computation/buildComputation/linkInventory.js @@ -2,22 +2,22 @@ * Performs a depth first traversal of the character tree, summing the container * and inventory contents on the way up the tree */ -export default function linkInventory(forest, dependencyGraph){ +export default function linkInventory(forest, dependencyGraph) { // The stack of properties to still navigate const stack = [...forest]; // The current containers we are inside of const containerStack = []; - while(stack.length){ + while (stack.length) { const top = stack[stack.length - 1]; const prop = top.node; - if (prop._computationDetails.inventoryChildrenVisited){ + if (prop._computationDetails.inventoryChildrenVisited) { if (prop.type === 'container') containerStack.pop(); stack.pop(); handleProp(prop, containerStack, dependencyGraph); } else { // Add all containers to the stack when we first visit them - if (prop.type === 'container'){ + if (prop.type === 'container') { containerStack.push(top.node); } // Push children onto the stack and mark this as children are visited @@ -27,18 +27,18 @@ export default function linkInventory(forest, dependencyGraph){ } } -function handleProp(prop, containerStack, dependencyGraph){ +function handleProp(prop, containerStack, dependencyGraph) { // Skip props that aren't part of the inventory if (prop.type !== 'item' && prop.type !== 'container') return; // Determine if this property is carried, items are carried by default let carried = prop.type === 'container' ? prop.carried : true; // Item-specific links - if (prop.type === 'item'){ - if (prop.attuned){ + if (prop.type === 'item') { + if (prop.attuned) { dependencyGraph.addLink('itemsAttuned', prop._id, 'attunedItem'); } - if (prop.equipped){ + if (prop.equipped) { dependencyGraph.addLink('weightEquipment', prop._id, 'equippedItem'); dependencyGraph.addLink('valueEquipment', prop._id, 'equippedItem'); } @@ -47,14 +47,14 @@ function handleProp(prop, containerStack, dependencyGraph){ // Get the parent container const container = containerStack[containerStack.length - 1]; - if (container){ + if (container) { // The container depends on this prop for its contents data dependencyGraph.addLink(container._id, prop._id, 'containerContents'); } else { // There is no parent container, the character totals depend on this prop dependencyGraph.addLink('weightTotal', prop._id, 'inventoryStats'); dependencyGraph.addLink('valueTotal', prop._id, 'inventoryStats'); - if (carried){ + if (carried) { dependencyGraph.addLink('weightCarried', prop._id, 'inventoryStats'); dependencyGraph.addLink('valueCarried', prop._id, 'inventoryStats'); } diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 4a83ab62..85a7b551 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -80,6 +80,15 @@ function linkAction(dependencyGraph, prop, { propsById }) { key: `resources.attributesConsumed[${index}].quantity`, }); }); + // Link conditions + prop.resources.conditions?.forEach((con, index) => { + // Link the property to its condition calculation + dependOnCalc({ + dependencyGraph, + prop, + key: `resources.conditions[${index}].condition`, + }); + }); } function linkAdjustment(dependencyGraph, prop) { diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index 86d23e80..c908aa18 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -8,14 +8,21 @@ export default function computeAction(computation, node) { } computeResources(computation, node); if (!prop.resources) return; - prop.resources.itemsConsumed.forEach(itemConsumed => { + prop.resources.conditions?.forEach(conObj => { + const condition = conObj.condition; + if (!condition) return; + if (condition.calculation && !condition.value) { + prop.insufficientResources = true; + } + }); + prop.resources.itemsConsumed?.forEach(itemConsumed => { if (!itemConsumed.itemId || itemConsumed.available < itemConsumed.quantity?.value) { prop.insufficientResources = true; } }); - prop.resources.attributesConsumed.forEach(attConsumed => { + prop.resources.attributesConsumed?.forEach(attConsumed => { if (!attConsumed.variableName) return; - if (attConsumed.available < attConsumed.quantity?.value) { + if (!(attConsumed.available >= attConsumed.quantity?.value)) { prop.insufficientResources = true; } }); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeContainer.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeContainer.js index 61b4b79a..0258692f 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeContainer.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeContainer.js @@ -1,11 +1,17 @@ import aggregate from './computeVariable/aggregate/index.js'; +import { safeStrip } from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js'; -export default function computeContainer(computation, node){ +export default function computeContainer(computation, node) { if (!node.data) node.data = {}; aggregateLinks(computation, node); + + // Clean up floating points + const prop = node.data; + prop.contentsWeight = safeStrip(prop.contentsWeight); + prop.carriedWeight = safeStrip(prop.carriedWeight); } -function aggregateLinks(computation, node){ +function aggregateLinks(computation, node) { computation.dependencyGraph.forEachLinkedNode( node.id, (linkedNode, link) => { @@ -13,7 +19,7 @@ function aggregateLinks(computation, node){ // Ignore inactive props if (linkedNode.data.inactive) return; // Aggregate inventory links - aggregate.inventory({node, linkedNode, link}); + aggregate.inventory({ node, linkedNode, link }); }, true // enumerate only outbound links ); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateInventory.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateInventory.js index 6dd01ba2..a7291388 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateInventory.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateInventory.js @@ -1,25 +1,25 @@ -export default function aggregateInventory({node, linkedNode, link}){ +export default function aggregateInventory({ node, linkedNode, link }) { let linkedProp = linkedNode.data || {}; const prop = node.data; - switch (link.data){ + switch (link.data) { case 'attunedItem': prop.baseValue = (prop.baseValue || 0) + 1; return; case 'equippedItem': - if (node.id === 'weightEquipment'){ + if (node.id === 'weightEquipment') { prop.baseValue = (prop.baseValue || 0) + weight(linkedProp); - } else if (node.id === 'valueEquipment'){ + } else if (node.id === 'valueEquipment') { prop.baseValue = (prop.baseValue || 0) + value(linkedProp); } return; case 'containerContents': // Add this property's weights and values to the container - if (!prop.weightless){ + if (!prop.weightless) { prop.contentsWeight = (prop.contentsWeight || 0) + weight(linkedProp); - if (prop.carried){ + if (prop.carried && !prop.contentsWeightless) { prop.carriedWeight = (prop.carriedWeight || 0) + carriedWeight(linkedProp); } } @@ -30,39 +30,39 @@ export default function aggregateInventory({node, linkedNode, link}){ return; case 'inventoryStats': - if (node.id === 'weightTotal'){ + if (node.id === 'weightTotal') { prop.baseValue = (prop.baseValue || 0) + weight(linkedProp); - } else if (node.id === 'valueTotal'){ + } else if (node.id === 'valueTotal') { prop.baseValue = (prop.baseValue || 0) + value(linkedProp); - } else if (node.id === 'weightCarried'){ + } else if (node.id === 'weightCarried') { prop.baseValue = (prop.baseValue || 0) + carriedWeight(linkedProp); - } else if (node.id === 'valueCarried'){ + } else if (node.id === 'valueCarried') { prop.baseValue = (prop.baseValue || 0) + carriedValue(linkedProp); } return; } } -function quantity(prop){ - if (typeof prop.quantity === 'number'){ +function quantity(prop) { + if (typeof prop.quantity === 'number') { return prop.quantity; } else { return 1; } } -function weight(prop){ +function weight(prop) { return (prop.weight || 0) * quantity(prop) + (prop.contentsWeight || 0); } -function carriedWeight(prop){ +function carriedWeight(prop) { return (prop.weight || 0) * quantity(prop) + (prop.carriedWeight || 0); } -function value (prop){ +function value(prop) { return (prop.value || 0) * quantity(prop) + (prop.contentsValue || 0); } -function carriedValue (prop){ +function carriedValue(prop) { return (prop.value || 0) * quantity(prop) + (prop.carriedValue || 0); } diff --git a/app/imports/api/engine/computation/computeComputation/computeToggles.js b/app/imports/api/engine/computation/computeComputation/computeToggles.js index 657305d6..a39e8dc4 100644 --- a/app/imports/api/engine/computation/computeComputation/computeToggles.js +++ b/app/imports/api/engine/computation/computeComputation/computeToggles.js @@ -5,8 +5,26 @@ export default function evaluateToggles(computation, node) { if (!toggles) return; toggles.forEach(toggle => { if ( - (!toggle.enabled && !toggle.disabled && toggle.condition && !toggle.condition.value) - || (toggle.disabled) + ( + // Toggle isn't set to constantly enabled or disabled + !toggle.enabled && + !toggle.disabled && + // Toggle is not disabled by another toggle targeting it + // Ancestor toggles would've handled this child anyway, + // and tag targeted toggles break the link + !toggle.deactivatedByToggle && + !toggle.deactivatedByAncestor && + // Toggle has a condition with a falsy value + toggle.condition && + !toggle.condition.value + ) + || ( + // Toggle is disabled manually + toggle.disabled && + // Toggle isn't deactivated by something else + !toggle.deactivatedByToggle && + !toggle.deactivatedByAncestor + ) ) { prop.inactive = true; prop.deactivatedByToggle = true; diff --git a/app/imports/api/engine/computation/utility/stripFloatingPointOddities.js b/app/imports/api/engine/computation/utility/stripFloatingPointOddities.js index 8e55685c..3415ff1a 100644 --- a/app/imports/api/engine/computation/utility/stripFloatingPointOddities.js +++ b/app/imports/api/engine/computation/utility/stripFloatingPointOddities.js @@ -1,3 +1,8 @@ -export default function stripFloatingPointOddities(num, precision = 12){ +export default function stripFloatingPointOddities(num, precision = 12) { return +parseFloat(num.toPrecision(precision)) } + +export function safeStrip(num, precision = 12) { + if (!Number.isFinite(num)) return num; + return stripFloatingPointOddities(num, precision); +} diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index 3de84914..163d938d 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -77,6 +77,7 @@ let ActionSchema = createPropertySchema({ 'resources.itemsConsumed': { type: Array, defaultValue: [], + max: 32, }, 'resources.itemsConsumed.$': { type: Object, @@ -104,6 +105,7 @@ let ActionSchema = createPropertySchema({ 'resources.attributesConsumed': { type: Array, defaultValue: [], + max: 32, }, 'resources.attributesConsumed.$': { type: Object, @@ -124,6 +126,30 @@ let ActionSchema = createPropertySchema({ type: 'fieldToCompute', optional: true, }, + 'resources.conditions': { + type: Array, + defaultValue: [], + max: 32, + }, + 'resources.conditions.$': { + type: Object, + }, + 'resources.conditions.$._id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + autoValue() { + if (!this.isSet) return Random.id(); + } + }, + 'resources.conditions.$.condition': { + type: 'fieldToCompute', + optional: true, + }, + 'resources.conditions.$.conditionNote': { + type: String, + optional: true, + max: STORAGE_LIMITS.calculation, + }, // Prevent the property from showing up in the log silent: { type: Boolean, diff --git a/app/imports/api/properties/Damages.js b/app/imports/api/properties/Damages.js index 190dd4b5..d01a2e0d 100644 --- a/app/imports/api/properties/Damages.js +++ b/app/imports/api/properties/Damages.js @@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; +import { SavingThrowSchema, ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js'; const DamageSchema = createPropertySchema({ // The roll that determines how much to damage the attribute @@ -32,6 +33,15 @@ const DamageSchema = createPropertySchema({ type: Boolean, optional: true, }, + save: { + type: SavingThrowSchema, + optional: true, + }, + 'save.damageFunction': { + type: 'fieldToCompute', + optional: true, + parseLevel: 'compile', + }, }); const ComputedOnlyDamageSchema = createPropertySchema({ @@ -40,6 +50,15 @@ const ComputedOnlyDamageSchema = createPropertySchema({ optional: true, parseLevel: 'compile', }, + save: { + type: ComputedOnlySavingThrowSchema, + optional: true, + }, + 'save.damageFunction': { + type: 'computedOnlyField', + optional: true, + parseLevel: 'compile', + }, }); const ComputedDamageSchema = new SimpleSchema() diff --git a/app/imports/api/properties/Triggers.js b/app/imports/api/properties/Triggers.js index 0bfd7d9d..250d6bda 100644 --- a/app/imports/api/properties/Triggers.js +++ b/app/imports/api/properties/Triggers.js @@ -18,6 +18,7 @@ const eventOptions = { const timingOptions = { before: 'Before', after: 'After', + afterChildren: 'After Children', } const actionPropertyTypeOptions = { @@ -91,7 +92,7 @@ let TriggerSchema = createPropertySchema({ 'extraTags.$._id': { type: String, regEx: SimpleSchema.RegEx.Id, - autoValue(){ + autoValue() { if (!this.isSet) return Random.id(); } }, diff --git a/app/imports/client/ui/components/ColorPicker.vue b/app/imports/client/ui/components/ColorPicker.vue index ef73f90d..b008ccfc 100644 --- a/app/imports/client/ui/components/ColorPicker.vue +++ b/app/imports/client/ui/components/ColorPicker.vue @@ -18,7 +18,7 @@ {{ label }} mdi-format-paint @@ -148,6 +148,7 @@ type: Number, default: undefined, }, + noColorChange: Boolean, }, data(){ return { colors: [ diff --git a/app/imports/client/ui/components/MarkdownText.vue b/app/imports/client/ui/components/MarkdownText.vue index bb133d27..eb0adfe8 100644 --- a/app/imports/client/ui/components/MarkdownText.vue +++ b/app/imports/client/ui/components/MarkdownText.vue @@ -9,6 +9,7 @@ diff --git a/app/imports/client/ui/markdownCofig.js b/app/imports/client/ui/markdownCofig.js index c0ff6a03..651ff39e 100644 --- a/app/imports/client/ui/markdownCofig.js +++ b/app/imports/client/ui/markdownCofig.js @@ -4,7 +4,6 @@ import DOMPurify from 'dompurify'; marked.setOptions({ breaks: true, gfm: true, - sanitizer: DOMPurify.sanitize, silent: true, smartLists: true, smartypants: true, diff --git a/app/imports/client/ui/properties/components/actions/ActionCard.vue b/app/imports/client/ui/properties/components/actions/ActionCard.vue index 0df8dc34..97d25c5e 100644 --- a/app/imports/client/ui/properties/components/actions/ActionCard.vue +++ b/app/imports/client/ui/properties/components/actions/ActionCard.vue @@ -71,9 +71,14 @@