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/creature/creatureProperties/methods/getSlotFillFilter.js b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js index 9558ff55..f3d82f75 100644 --- a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js +++ b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js @@ -18,23 +18,31 @@ export default function getSlotFillFilter({ slot, libraryIds }) { }] }); } else if (slot.type === 'class') { - filter.$and.push({ - $or: [{ - type: 'classLevel', - }, { - slotFillerType: 'classLevel', - }] - }); + const classLevelFilter = { + type: 'classLevel', + }; + const slotFillerFilter = { + slotFillerType: 'classLevel', + }; + + // Match variable name or tags if (slot.variableName) { - filter.variableName = slot.variableName; + classLevelFilter.variableName = slot.variableName; + slotFillerFilter.libraryTags = slot.variableName; } // Only search for levels the class needs if (slot.missingLevels && slot.missingLevels.length) { - filter.level = { $in: slot.missingLevels }; + classLevelFilter.level = { $in: slot.missingLevels }; + slotFillerFilter['cache.node.level'] = { $in: slot.missingLevels }; } else { - filter.level = { $gt: slot.level || 0 }; + classLevelFilter.level = { $gt: slot.level || 0 }; + slotFillerFilter['cache.node.level'] = { $gt: slot.level || 0 }; } + + filter.$and.push({ + $or: [classLevelFilter, slotFillerFilter] + }); } let tagsOr = []; let tagsNin = []; diff --git a/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js b/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js index deb55bc7..469cd0fc 100644 --- a/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js +++ b/app/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js @@ -105,6 +105,8 @@ function insertPropertyFromNode(nodeId, ancestors, order) { // Convert all references into actual nodes nodes = reifyNodeReferences(nodes); + // Refetch the root node, it might have been reified + node = nodes[0] || node; // set libraryNodeIds storeLibraryNodeReferences(nodes); 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 b08e807c..b1923a4d 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 @@ -134,7 +130,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/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index 663922a5..646bd97c 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -16,7 +16,7 @@ export default function computeAction(computation, node) { }); 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/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/creature/CreatureFormDialog.vue b/app/imports/client/ui/creature/CreatureFormDialog.vue index f3f3a058..f4c5ce87 100644 --- a/app/imports/client/ui/creature/CreatureFormDialog.vue +++ b/app/imports/client/ui/creature/CreatureFormDialog.vue @@ -10,6 +10,7 @@ diff --git a/app/imports/client/ui/creature/slots/LevelUpDialog.vue b/app/imports/client/ui/creature/slots/LevelUpDialog.vue index dcf4ce08..7b49f6f7 100644 --- a/app/imports/client/ui/creature/slots/LevelUpDialog.vue +++ b/app/imports/client/ui/creature/slots/LevelUpDialog.vue @@ -49,7 +49,7 @@ :key="libraryNode._id" :model="libraryNode" :data-id="libraryNode._id" - :class="{disabled: isDisabled(libraryNode)}" + :class="{disabled: isDisabled(libraryNode) || libraryNode._disabledBySlotFillerCondition}" > - + @@ -120,11 +121,12 @@ column align-center justify-center - class="ma-3" + class="ma-3 mt-8" > Load More @@ -247,6 +249,13 @@ export default { }); return { or, not }; }, + filledLevels() { + return LibraryNodes.find({ + _id: { $in: this.selectedNodeIds } + }).map( + node => node.level || node.cache?.node?.level || 0 + ).sort((a, b) => a - b); + } }, watch: { selectedNodeIds(selectedIds, oldSelectedIds) { @@ -257,14 +266,19 @@ export default { if (!addedId) return; const addedNode = LibraryNodes.findOne(addedId); if (!addedNode) return; - // Tick any unchecked nodes of a lower level, but only one per level + // Check which levels are already backfilled const backFilledLevels = new Set(); + const sortedIds = LibraryNodes.find({ + _id: { $in: selectedIds } + }).map(node => backFilledLevels.add(node.level || node.cache?.node?.level || 0)); + // Tick any unchecked nodes of a lower level, but only one per level this.libraryNodes.forEach(node => { if ( !selectedIds.includes(node._id) - && node.level < addedNode.level + && (node.level < addedNode.level) && !backFilledLevels.has(node.level) && !this.isDisabled(node) + && !node._disabledBySlotFillerCondition ) { selectedIds.push(node._id); backFilledLevels.add(node.level) @@ -278,12 +292,26 @@ export default { _id: { $in: selectedIds } }, { sort: { level: 1, name: 1, order: 1 } - }).map(node => node._id); + }) + .fetch() + .sort((a, b) => (a.level || a.cache?.node?.level || 0) - (b.level || b.cache?.node?.level || 0)) + .map(node => node._id); // Only update if the order changed if (!isEqual(this.selectedNodeIds, sortedIds)) { this.selectedNodeIds = sortedIds; } - } + }, + activeCount(val) { + // Still loading fillers + if (!this._subs['classFillers'].ready()) return; + // Can load more, and not showing enough active choices, so load more + if ( + this.currentLimit < this.countAll + && val < 20 + ) { + this.loadMore(); + } + }, }, methods: { loadMore() { @@ -300,12 +328,10 @@ export default { }); }, isDisabled(node) { - return node._disabledBySlotFillerCondition || - node._disabledByAlreadyAdded || - ( - node._disabledByQuantityFilled && - !this.selectedNodeIds.includes(node._id) - ) + const selected = this.selectedNodeIds.includes(node._id); + return node._disabledByAlreadyAdded + || ( node._disabledByQuantityFilled && !selected ) + || ( this.filledLevels.includes(node.level || node.cache?.node?.level || 0) && !selected ) }, }, meteor: { @@ -338,6 +364,10 @@ export default { countAll() { return this._subs['classFillers'].data('countAll'); }, + activeCount() { + if (!this.libraryNodes) return; + return this.libraryNodes.length - (this.disabledNodeCount || 0); + }, alreadyAdded() { let added = new Set(); if (!this.model.unique) return added; @@ -397,6 +427,9 @@ export default { // Mark classFillers whose condition isn't met or are too big to fit // the quantity to fill nodes.forEach(node => { + if (node.cache?.node) { + node.level = node.cache.node.level; + } if (node.slotFillerCondition) { try { let parseNode = parse(node.slotFillerCondition); @@ -430,6 +463,7 @@ export default { node._disabledByAlreadyAdded = true; } }); + nodes.sort((a, b) => a.level - b.level); this.disabledNodeCount = disabledNodeCount; return nodes; }, diff --git a/app/imports/client/ui/creature/slots/SlotCardsToFill.vue b/app/imports/client/ui/creature/slots/SlotCardsToFill.vue index cddbc800..99bad92a 100644 --- a/app/imports/client/ui/creature/slots/SlotCardsToFill.vue +++ b/app/imports/client/ui/creature/slots/SlotCardsToFill.vue @@ -1,39 +1,35 @@ diff --git a/app/imports/client/ui/library/LibraryNodeExpansionContent.vue b/app/imports/client/ui/library/LibraryNodeExpansionContent.vue index f6168488..59d0cf77 100644 --- a/app/imports/client/ui/library/LibraryNodeExpansionContent.vue +++ b/app/imports/client/ui/library/LibraryNodeExpansionContent.vue @@ -1,5 +1,5 @@